Vulkan渲染纹理四边形遇OBS闪烁损坏及DEVICE_LOST崩溃问题
Vulkan 2D引擎SpriteBatch复刻中的DEVICE_LOST与画面损坏问题
我正在用Vulkan实现复刻XNA/MonoGame SpriteBatch功能的基础2D游戏引擎,目标是渲染大量带独立变换(旋转、平移、缩放)的纹理四边形。目前遇到两个核心问题:
- OBS捕获画面时,部分区域出现闪烁或损坏
- 低端硬件上绘制量超过数百个时,
vkQueueSubmit返回DEVICE_LOST直接崩溃;高端硬件也会每数小时随机崩溃,崩溃前画面会短暂出现类似OBS捕获的闪烁损坏现象
验证层未报告任何错误或警告,初步怀疑是同步机制出错,且硬件性能越低崩溃概率越高。即使在帧开始/结束处插入vkQueueWaitIdle,问题仍然存在,似乎和多帧并行设计无关。
以下是保留所有Vulkan命令和同步代码的帧循环精简版本:
vkWaitForFences(instance.Device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); auto aquireImageResult = vkAcquireNextImageKHR(instance.Device, swapChain.SwapChain, UINT64_MAX, imageAvailableSemaphore[currentFrame], VK_NULL_HANDLE, &imageIndex); if (aquireImageResult == VK_ERROR_OUT_OF_DATE_KHR || aquireImageResult == VK_SUBOPTIMAL_KHR) { return ... } if (aquireImageResult != VK_SUCCESS) { return GenericFailure; } if (inFlightImages[imageIndex] != VK_NULL_HANDLE) vkWaitForFences(instance.Device, 1, &inFlightImages[imageIndex], VK_TRUE, UINT64_MAX); inFlightImages[imageIndex] = inFlightFences[currentFrame]; vkResetFences(instance.Device, 1, &inFlightFences[currentFrame]); // We re-record command buffers every frame auto& rootCommandBuffer = instance.CommandBuffers[imageIndex]; auto& frameBuffer = instance.FrameBuffers[imageIndex]; VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = 0; beginInfo.pInheritanceInfo = nullptr; vkResetCommandBuffer(rootCommandBuffer, 0); // This is done implicitly on vkBeginCommandBuffer because of the flags on the command pool, but better do it explicitly if (vkBeginCommandBuffer(rootCommandBuffer, &beginInfo) != VK_SUCCESS) throw std::runtime_error("Failed to begin recording command buffer."); VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = instance.RenderPass; renderPassInfo.framebuffer = frameBuffer; renderPassInfo.renderArea.offset = { 0, 0 }; renderPassInfo.renderArea.extent = swapChain.Extent; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; vkCmdBeginRenderPass(rootCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); // "Draws" are submitted in C# land - a struct of (image, trans_matrix, color) is written into a host_coherent buffer for each draw. // Draw is being done with instanceCount = _numberOfDraws, geometrie is created in vertex shader (just a quad) // Basically, these two commands are executed exaclty once per frame: // Start C# var setsToBeBound = stackalloc VkDescriptorSet[2] { _bufferDescriptorSet, _imageDescriptorSet }; VulkanNative.vkCmdBindDescriptorSets(_vkCommandBuffer, VkPipelineBindPoint.VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineHandle.Pipeline->VkPipelineLayout, 0, 2, setsToBeBound, 0, null); VulkanNative.vkCmdDraw(_vkCommandBuffer, 4, (uint)_numberOfDraws, 0, 0); // End C# vkCmdEndRenderPass(rootCommandBuffer); if (vkEndCommandBuffer(rootCommandBuffer) != VK_SUCCESS) throw std::runtime_error("Failed to end command buffer recording."); VkSemaphore waitSemaphores[] = { imageAvailableSemaphore[currentFrame] }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkSemaphore signalSemaphores[] = { renderFinishedSemaphore[currentFrame] }; VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &rootCommandBuffer; auto queueSubmitResult = vkQueueSubmit(instance.GraphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]); if (queueSubmitResult != VK_SUCCESS) throw std::runtime_error(std::string("Failed to submit draw command buffer. Code: ") + std::to_string(queueSubmitResult)); // Here a crash can happen with code DEVICE_LOST VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; VkSwapchainKHR swapChains[] = { swapChain.SwapChain }; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = swapChains; presentInfo.pImageIndices = &imageIndex; presentInfo.pResults = nullptr; // Optional auto presentResult = vkQueuePresentKHR(instance.PresentQueue, &presentInfo); if (presentResult == VK_ERROR_OUT_OF_DATE_KHR || presentResult == VK_SUBOPTIMAL_KHR) { instance.PendingResize = true; } else if (presentResult != VK_SUCCESS) { throw ... } currentFrame = (currentFrame + 1) % maxFramesInFlight;
排查与修复方向
1. 实例化缓冲区的同步问题
你用host-coherent缓冲区存储绘制数据(矩阵、颜色等),但必须确保GPU读取时CPU已经完成写入:
- 即使是host-coherent内存,也可以在CPU写入完成后调用
vkFlushMappedMemoryRanges显式刷新,避免驱动延迟导致GPU读到旧数据 - 检查缓冲区的
VkBufferUsageFlags是否包含VK_BUFFER_USAGE_INSTANCE_BUFFER_BIT(因为你用的是实例化绘制),内存属性是否正确设置为VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT - 在
vkCmdDraw前,添加缓冲区的内存屏障,确保CPU写入的可见性:VkBufferMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; barrier.buffer = instanceBuffer; barrier.offset = 0; barrier.size = VK_WHOLE_SIZE; vkCmdPipelineBarrier(rootCommandBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1, &barrier, 0, nullptr);
2. 交换链图像布局与渲染通道同步
OBS捕获的画面损坏通常和交换链图像布局转换不完整有关:
- 检查RenderPass的subpass依赖,确保图像布局从
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR(获取后)转换到VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL(渲染时),渲染完成后转换回VK_IMAGE_LAYOUT_PRESENT_SRC_KHR - 确认RenderPass的颜色附件load操作是
VK_ATTACHMENT_LOAD_OP_CLEAR(如果每一帧都清屏),store操作是VK_ATTACHMENT_STORE_OP_STORE,确保渲染结果正确保存到交换链图像
3. 信号量等待阶段的修正
当前等待阶段只设置了VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,但你的绘制需要读取实例缓冲区,应该把等待阶段扩展到VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,避免GPU提前读取缓冲区:
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
4. 资源泄漏与GPU内存检查
DEVICE_LOST在低端硬件频繁出现,可能是GPU内存耗尽:
- 检查所有纹理、缓冲区资源是否在不需要时正确释放,用Vulkan验证层的内存追踪功能排查泄漏
- 尝试降低
maxFramesInFlight到1,强制单帧渲染,若仍崩溃则说明问题不在多帧同步,而是绘制逻辑或资源管理的问题
5. 驱动兼容性测试
更新显卡驱动到最新版本,部分旧驱动对实例化绘制或host-coherent内存的处理存在bug;同时测试不同显卡品牌(Nvidia/AMD/Intel),缩小问题范围
内容的提问来源于stack exchange,提问作者Whosdatdev
相关产品推荐
相关产品推荐

