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
相关产品推荐
相关产品推荐

