通过 OBS 查看时,使用 vulkan 闪烁/损坏渲染的纹理四边形

Textured quads rendered using vulkan flicker/corrupt, when viewed through OBS

提问人:Whosdatdev 提问时间:11/12/2023 更新时间:11/15/2023 访问量:86

问:

我正在使用 vulkan 实现一个基本的 2D 游戏引擎。我想从 XNA/MonoGame 重新实现 SpriteBatch,所以基本上我想绘制大量带有单独转换(旋转、平移、缩放)的纹理四边形。

我的应用程序可以正常工作,但有两个问题:

  • 通过OBS查看时,某些地方会出现一些闪烁或图像“损坏”
  • 在低端硬件上,应用程序会立即崩溃,因为当有超过几百次绘制时,会返回vkQueueSubmitDEVICE_LOST

一个测试,用随机旋转绘制 30000 张图像:img1

这看起来符合预期。通过OBS查看测试应用,你会看到这样:

img2

在高端硬件上,应用程序每隔几个小时就会随机崩溃一次()。就在崩溃之前,窗口上显示的图像短暂地看起来像始终可以通过 OBS 观察到的图像(闪烁、损坏)。DEVICE_LOST

验证层未报告任何错误或警告。我最初的怀疑是我搞砸了同步,导致较慢的硬件崩溃的可能性更高。

我的代码基于 Sascha Willems vulkan 教程 (https://vulkan-tutorial.com/) (尽管经过大量改编)。这是帧外观的(大幅缩小)版本(我保留了所有 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;

让我质疑我的理智的是,即使我在帧的末尾/开头插入 a,错误仍然会发生(因此,由于整个应用程序支持多帧飞行设计,这似乎不是一个错误)。vkQueueWaitIdle

C++ Vulkan 精灵批处理

评论


答: 暂无答案