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

Qt框架下跨多帧释放Vulkan缓冲区资源的问题求助

Qt Vulkan应用中缓冲区资源释放的验证错误解决方案

错误信息

vkDebug: Validation Error: [ VUID-vkDestroyBuffer-buffer-00922 ] | MessageID = 0xe4549c11 | vkDestroyBuffer(): can't be called on VkBuffer 0xd68cbb0000000091[] that is currently in use by VkCommandBuffer 0x4959c00[].

场景说明

在基于Qt框架的Vulkan应用中,缓冲区用于逐帧渲染,当创建同名新文本时需要释放对应资源,但直接调用释放会触发上述验证错误——因为缓冲区仍在被未完成的命令缓冲区占用。当前处理代码如下:

void TextModule::createText(const std::string &fontName, uint32_t fontSize, const std::string &textName, const std::string &text, float scale,
                          glm::vec2 middlePos, glm::vec2 windowSize, glm::vec4 color, e_TextRenderType renderType)
{
    FontInfo fInfo = s_fontManager.loadedFont(fontName, fontSize);


    if (m_texts.find(textName) != m_texts.end())
    {
        Window *vw = MainWindow::instance()->vulkanWindow();
        for(int i = 0; i < m_instruments->concurrentFrameCount; i++)
        {
            m_texts[textName].bufArr[i].releaseResources();
        }
    }

//more code

    for(int i = 0; i < m_instruments->concurrentFrameCount; i++)
    {
        textInfo.bufArr[i].createBuffer({textInfo.textVertices.size() * sizeof(base::graphics::ColorTexVertex)}, {BDSH::VX});
        textInfo.bufArr[i].set(textInfo.textVertices.data(), textInfo.textVertices.size() * sizeof(base::graphics::ColorTexVertex));
        textInfo.bufArr[i].mapWholeMemory();
        textInfo.bufArr[i].update();
        textInfo.bufArr[i].unMapMemory();
    }
    m_texts[textName] = textInfo;
}

查看Qt源码后发现,其内部会等待每帧的栅栏(fence),但这些栅栏属于私有类无法直接访问,导致无法等待所有栅栏完成后再释放缓冲区。不想修改Qt源码,寻求替代方案。


解决方案

1. 延迟释放机制(推荐)

维护一个待释放资源队列,在Qt的Vulkan帧循环完成回调中处理释放:

  • 当需要释放资源时,不要立即调用releaseResources(),而是将资源对象(或其句柄)加入线程安全的待释放队列。
  • 重写QVulkanWindow的frameEnded()方法,这个回调触发时Qt已经完成了当前帧的栅栏等待,此时可以安全执行资源释放。

示例代码片段:

// 在TextModule中维护待释放队列
std::mutex m_pendingReleaseMutex;
std::vector<BufferResource> m_pendingReleases;

// 修改释放逻辑
if (m_texts.find(textName) != m_texts.end())
{
    std::lock_guard<std::mutex> lock(m_pendingReleaseMutex);
    for(int i = 0; i < m_instruments->concurrentFrameCount; i++)
    {
        m_pendingReleases.push_back(std::move(m_texts[textName].bufArr[i]));
    }
    m_texts.erase(textName);
}

// 在自定义QVulkanWindow子类中
void MyVulkanWindow::frameEnded()
{
    QVulkanWindow::frameEnded(); // 确保Qt完成帧处理

    // 处理待释放资源
    std::vector<BufferResource> toRelease;
    {
        std::lock_guard<std::mutex> lock(textModule->m_pendingReleaseMutex);
        toRelease.swap(textModule->m_pendingReleases);
    }

    for (auto& buf : toRelease)
    {
        buf.releaseResources();
    }
}

2. 手动等待队列空闲

通过QVulkanWindow获取设备和图形队列,提交空命令缓冲区并等待完成,确保之前的命令都执行完毕后再释放资源:

if (m_texts.find(textName) != m_texts.end())
{
    QVulkanWindow* vw = MainWindow::instance()->vulkanWindow();
    VkDevice device = vw->device();
    VkQueue queue = vw->queue();

    // 创建临时命令缓冲区
    VkCommandBufferAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = vw->commandPool();
    allocInfo.commandBufferCount = 1;

    VkCommandBuffer cmdBuf;
    vkAllocateCommandBuffers(device, &allocInfo, &cmdBuf);

    // 提交空命令
    VkCommandBufferBeginInfo beginInfo{};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    vkBeginCommandBuffer(cmdBuf, &beginInfo);
    vkEndCommandBuffer(cmdBuf);

    // 创建栅栏并等待
    VkFenceCreateInfo fenceInfo{};
    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
    VkFence fence;
    vkCreateFence(device, &fenceInfo, nullptr, &fence);

    vkResetFences(device, 1, &fence);
    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &cmdBuf;
    vkQueueSubmit(queue, 1, &submitInfo, fence);
    vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);

    // 清理临时资源
    vkDestroyFence(device, fence, nullptr);
    vkFreeCommandBuffers(device, vw->commandPool(), 1, &cmdBuf);

    // 安全释放缓冲区
    for(int i = 0; i < m_instruments->concurrentFrameCount; i++)
    {
        m_texts[textName].bufArr[i].releaseResources();
    }
}

注意:该方法会阻塞调用线程,可能影响帧率,仅适合资源更新频率低的场景。

3. 复用缓冲区而非销毁重建

如果缓冲区大小变化不大,直接重新映射内存更新数据,避免频繁销毁重建:

if (m_texts.find(textName) != m_texts.end())
{
    auto& existingText = m_texts[textName];
    size_t requiredSize = textInfo.textVertices.size() * sizeof(base::graphics::ColorTexVertex);
    bool needRecreate = false;

    // 检查缓冲区是否足够大
    for(int i = 0; i < m_instruments->concurrentFrameCount; i++)
    {
        if (existingText.bufArr[i].size() < requiredSize)
        {
            needRecreate = true;
            break;
        }
    }

    if (needRecreate)
    {
        for(int i = 0; i < m_instruments->concurrentFrameCount; i++)
        {
            existingText.bufArr[i].releaseResources();
            existingText.bufArr[i].createBuffer({requiredSize}, {BDSH::VX});
        }
    }

    // 更新缓冲区数据
    for(int i = 0; i < m_instruments->concurrentFrameCount; i++)
    {
        existingText.bufArr[i].mapWholeMemory();
        existingText.bufArr[i].set(textInfo.textVertices.data(), requiredSize);
        existingText.bufArr[i].update();
        existingText.bufArr[i].unMapMemory();
    }
    return; // 跳过后续创建逻辑
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:53:17