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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:28:16