Support triple buffering and improve buffer sync

Increase MAX_FRAMES_IN_FLIGHT to 3 and cap swapchain images accordingly
Replace vkDeviceWaitIdle with per-copy fence, extend wait timeout and
add diagnostic logging and counters Add stronger error logging and
cleanup on vertex/index buffer creation and copy failures; reset handles
to VK_NULL_HANDLE on failure
This commit is contained in:
ubuntu1804 2025-11-11 09:14:14 +08:00
parent 90d770dd85
commit 95b83d5ed3
4 changed files with 55 additions and 20 deletions

View File

@ -513,9 +513,15 @@ void VulkanRenderer::recordCommandBuffer(VkCommandBuffer commandBuffer,
}
if (!createVertexBuffer(circleVertices, m_circleVertexBuffer, m_circleVertexMemory)) {
logError("Failed to create circle vertex buffer - skipping geometry rendering");
m_circleVertexBuffer = VK_NULL_HANDLE;
m_circleVertexMemory = VK_NULL_HANDLE;
return;
}
if (!createIndexBuffer(circleIndices, m_circleIndexBuffer, m_circleIndexMemory)) {
logError("Failed to create circle index buffer - skipping geometry rendering");
m_circleIndexBuffer = VK_NULL_HANDLE;
m_circleIndexMemory = VK_NULL_HANDLE;
return;
}
m_circleIndexCount = circleIndices.size();
@ -531,9 +537,15 @@ void VulkanRenderer::recordCommandBuffer(VkCommandBuffer commandBuffer,
}
if (!createVertexBuffer(waveVertices, m_waveVertexBuffer, m_waveVertexMemory)) {
logError("Failed to create wave vertex buffer - skipping geometry rendering");
m_waveVertexBuffer = VK_NULL_HANDLE;
m_waveVertexMemory = VK_NULL_HANDLE;
return;
}
if (!createIndexBuffer(waveIndices, m_waveIndexBuffer, m_waveIndexMemory)) {
logError("Failed to create wave index buffer - skipping geometry rendering");
m_waveIndexBuffer = VK_NULL_HANDLE;
m_waveIndexMemory = VK_NULL_HANDLE;
return;
}
m_waveIndexCount = waveIndices.size();
@ -1759,8 +1771,13 @@ bool VulkanRenderer::createVertexBuffer(const std::vector<Vertex>& vertices,
if (!copyBuffer(stagingBuffer, buffer, bufferSize)) {
logError("Failed to copy staging buffer to vertex buffer");
// 清理已创建的目标 buffer 和 staging buffer
vkDestroyBuffer(m_device, buffer, nullptr);
vkFreeMemory(m_device, memory, nullptr);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
buffer = VK_NULL_HANDLE;
memory = VK_NULL_HANDLE;
return false;
}
@ -1811,8 +1828,13 @@ bool VulkanRenderer::createIndexBuffer(const std::vector<uint16_t>& indices,
if (!copyBuffer(stagingBuffer, buffer, bufferSize)) {
logError("Failed to copy staging buffer to index buffer");
// 清理已创建的目标 buffer 和 staging buffer
vkDestroyBuffer(m_device, buffer, nullptr);
vkFreeMemory(m_device, memory, nullptr);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
buffer = VK_NULL_HANDLE;
memory = VK_NULL_HANDLE;
return false;
}
@ -1864,13 +1886,13 @@ bool VulkanRenderer::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, uint64_t
return false;
}
// Wait for device to be idle to avoid queue contention
// This ensures no other operations are using the queue
VkResult idleResult = vkDeviceWaitIdle(m_device);
if (idleResult != VK_SUCCESS) {
logError("Failed to wait for device idle before buffer copy");
return false;
}
// Note: 移除 vkDeviceWaitIdle改用 fence 精确同步
// vkDeviceWaitIdle 在三缓冲场景下会导致等待失败
// 因为双缓冲的 fence 机制只能确保 2 帧的同步
static int copyCount = 0;
std::cout << "copyBuffer called (#" << copyCount++ << "), size=" << size
<< " bytes" << std::endl;
// Create a one-time command pool if not exists
if (m_transferCommandPool == VK_NULL_HANDLE) {
@ -1947,26 +1969,34 @@ bool VulkanRenderer::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, uint64_t
result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, fence);
if (result != VK_SUCCESS) {
std::cerr << "vkQueueSubmit failed with error code: " << result << std::endl;
logError("Failed to submit copy command");
vkDestroyFence(m_device, fence, nullptr);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
}
// Wait for the fence with a reasonable timeout (1 second)
result = vkWaitForFences(m_device, 1, &fence, VK_TRUE, 1000000000); // 1 second in nanoseconds
std::cout << "Waiting for fence (timeout=5s)..." << std::endl;
// Wait for the fence with extended timeout for triple buffering (5 seconds)
// 三缓冲场景下需要更长的等待时间,因为可能有多帧在 GPU 执行
result = vkWaitForFences(m_device, 1, &fence, VK_TRUE, 5000000000ULL); // 5 seconds in nanoseconds
if (result == VK_TIMEOUT) {
logError("Timeout waiting for fence after copy - queue may be busy");
std::cerr << "TIMEOUT: vkWaitForFences exceeded 5 seconds - GPU may be overloaded" << std::endl;
logError("Timeout waiting for fence after copy (5s) - queue may be busy with triple buffering");
vkDestroyFence(m_device, fence, nullptr);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
} else if (result != VK_SUCCESS) {
std::cerr << "vkWaitForFences failed with error code: " << result << std::endl;
logError("Failed to wait for fence after copy");
vkDestroyFence(m_device, fence, nullptr);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
}
std::cout << "copyBuffer completed successfully" << std::endl;
// Cleanup fence
vkDestroyFence(m_device, fence, nullptr);
@ -2753,14 +2783,14 @@ void VulkanRenderer::drawText(VkCommandBuffer commandBuffer, int frameCount,
}
if (!createVertexBuffer(vertices, textVertexBuffer, textVertexMemory)) {
logError("Failed to create text vertex buffer - skipping text rendering");
return;
}
if (!createIndexBuffer(indices, textIndexBuffer, textIndexMemory)) {
logError("Failed to create text index buffer - skipping text rendering");
vkDestroyBuffer(m_device, textVertexBuffer, nullptr);
vkFreeMemory(m_device, textVertexMemory, nullptr);
textVertexBuffer = VK_NULL_HANDLE;
textVertexMemory = VK_NULL_HANDLE;
return;
}

View File

@ -389,8 +389,8 @@ private:
// 错误回调
void (*m_errorCallback)(const char*);
// 最大帧数
static const int MAX_FRAMES_IN_FLIGHT = 2;
// 最大帧数(支持三缓冲)
static const int MAX_FRAMES_IN_FLIGHT = 3;
};
#endif // VULKANRENDERER_H

View File

@ -478,11 +478,16 @@ bool VulkanWidget::createSwapchain()
qDebug() << "Final swapchain extent:" << m_surfaceWidth << "x" << m_surfaceHeight;
// Determine image count
// Determine image count - limit to MAX_FRAMES_IN_FLIGHT for proper synchronization
uint32_t imageCount = capabilities.minImageCount + 1;
if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) {
imageCount = capabilities.maxImageCount;
}
// 确保不超过 MAX_FRAMES_IN_FLIGHT避免三缓冲同步问题
if (imageCount > MAX_FRAMES_IN_FLIGHT) {
qDebug() << "Limiting swapchain images from" << imageCount << "to" << MAX_FRAMES_IN_FLIGHT;
imageCount = MAX_FRAMES_IN_FLIGHT;
}
VkSwapchainCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
@ -539,8 +544,8 @@ bool VulkanWidget::createSwapchain()
m_swapchainImageViews[i] = reinterpret_cast<void*>(imageView);
}
qDebug() << "Swapchain created successfully with" << imageCount << "images, size:"
<< m_surfaceWidth << "x" << m_surfaceHeight;
qDebug() << "Swapchain created successfully with" << imageCount << "images (MAX_FRAMES_IN_FLIGHT="
<< MAX_FRAMES_IN_FLIGHT << "), size:" << m_surfaceWidth << "x" << m_surfaceHeight;
return true;
}

View File

@ -261,8 +261,8 @@ private:
int m_lockPaintFrameCount;
int m_lockCount;
// 常量
static const int MAX_FRAMES_IN_FLIGHT = 2;
// 常量(支持三缓冲)
static const int MAX_FRAMES_IN_FLIGHT = 3;
};
#endif // VULKANWIDGET_H