为什么在使用VK_PRESENT_MODE_FIFO_KHR呈现模式调整交换链图像大小时出现白色闪烁

Why I got white blinking while resizing resizing the images of the swapchain with VK_PRESENT_MODE_FIFO_KHR present mode?

本文关键字:交换 调整 图像 白色 小时 模式 闪烁 VK PRESENT KHR FIFO      更新时间:2023-10-16

调整交换链图像的大小时(因为窗口大小发生了变化(,我会出现一些白色闪烁。我真的不明白为什么是这个问题的根源。我只有在将VK_PRESENT_MODE_FIFO_KHR呈现模式与我的Intel(R(UHD Graphics 630集成GPU一起使用时才有此问题,GeForce GTX 1050没有此问题。我发现有不同的行为取决于GPU真的很好奇Vulkan。

也许我试图实现的理想解决方案是有一个交换链,它总是处理屏幕的大小,如果可能的话,只对可见部分进行闪电攻击?

这是我的swapchain调整大小代码(由于我重做了一些可以避免的操作,所以远不是最佳的(。

bool resize_swapchain(VK_Renderer* renderer, Window* window) {
assert(renderer);
VkResult    res;
clear_swapchain(renderer);
// Build the swapchain
// Get the list of VkFormats that are supported:
get_enumeration(vkGetPhysicalDeviceSurfaceFormatsKHR,
VkSurfaceFormatKHR,
surface_formats,
"Failed to get physical device surface formats.n",
"Found %d surface formats.n",
renderer->physical_device,
renderer->surface);
// If the format list includes just one entry of VK_FORMAT_UNDEFINED,
// the surface has no preferred format. Otherwise, at least one
// supported format will be returned.
if (surface_formats.size() == 1 && surface_formats[0].format == VK_FORMAT_UNDEFINED) {
renderer->surface_format = VK_FORMAT_B8G8R8A8_UNORM;
} else {
renderer->surface_format = surface_formats[0].format;
}
VkSurfaceCapabilitiesKHR    surface_capabilities;
res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(renderer->physical_device, renderer->surface, &surface_capabilities);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to get physical device surface capabilities.n");
clear_swapchain(renderer);
return false;
}
get_enumeration(vkGetPhysicalDeviceSurfacePresentModesKHR,
VkPresentModeKHR,
present_modes,
"Failed to get physical device surface present modes.n",
"Found %d present modes.n",
renderer->physical_device,
renderer->surface);
// width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
if (surface_capabilities.currentExtent.width == 0xFFFFFFFF) {
// If the surface size is undefined, the size is set to
// the size of the images requested.
renderer->swapchain_extent.width = window->size.x;
renderer->swapchain_extent.height = window->size.y;
if (renderer->swapchain_extent.width < surface_capabilities.minImageExtent.width) {
renderer->swapchain_extent.width = surface_capabilities.minImageExtent.width;
} else if (renderer->swapchain_extent.width > surface_capabilities.maxImageExtent.width) {
renderer->swapchain_extent.width = surface_capabilities.maxImageExtent.width;
}
if (renderer->swapchain_extent.height < surface_capabilities.minImageExtent.height) {
renderer->swapchain_extent.height = surface_capabilities.minImageExtent.height;
} else if (renderer->swapchain_extent.height > surface_capabilities.maxImageExtent.height) {
renderer->swapchain_extent.height = surface_capabilities.maxImageExtent.height;
}
} else {
// If the surface size is defined, the swap chain size must match
renderer->swapchain_extent = surface_capabilities.currentExtent;
}
// The FIFO present mode is guaranteed by the spec to be supported
#if defined(FL_PROFILING_MODE)
VkPresentModeKHR    swapchain_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
#else
VkPresentModeKHR    swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
#endif
// Determine the number of VkImage's to use in the swap chain.
// We need to acquire only 1 presentable image at at time.
// Asking for minImageCount images ensures that we can acquire
// 1 presentable image as long as we present it before attempting
// to acquire another.
uint32_t    desired_number_of_swapchain_images = surface_capabilities.minImageCount;
VkSurfaceTransformFlagBitsKHR   surface_transform;
if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
surface_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
} else {
surface_transform = surface_capabilities.currentTransform;
}
// Find a supported composite alpha mode - one of these is guaranteed to be set
VkCompositeAlphaFlagBitsKHR composite_alpha_flag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
// TODO change the order if we want to be able to blend the window of our application with the Windows Desktop
VkCompositeAlphaFlagBitsKHR composite_alpha_flags[4] = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
};
for (uint32_t i = 0; i < sizeof(composite_alpha_flags); i++) {
if (surface_capabilities.supportedCompositeAlpha & composite_alpha_flags[i]) {
composite_alpha_flag = composite_alpha_flags[i];
break;
}
}
VkSwapchainCreateInfoKHR    swapchain_info = {};
swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_info.pNext = nullptr;
swapchain_info.surface = renderer->surface;
swapchain_info.minImageCount = desired_number_of_swapchain_images;
swapchain_info.imageFormat = renderer->surface_format;
swapchain_info.imageExtent.width = renderer->swapchain_extent.width;
swapchain_info.imageExtent.height = renderer->swapchain_extent.height;
swapchain_info.preTransform = surface_transform;
swapchain_info.compositeAlpha = composite_alpha_flag;
swapchain_info.imageArrayLayers = 1;
swapchain_info.presentMode = swapchain_present_mode;
swapchain_info.oldSwapchain = nullptr;
swapchain_info.clipped = true;
swapchain_info.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_info.queueFamilyIndexCount = 0;
swapchain_info.pQueueFamilyIndices = nullptr;
uint32_t    queue_family_indices[2] = {(uint32_t)renderer->graphics_queue_family_index, (uint32_t)renderer->present_queue_family_index};
if (renderer->graphics_queue_family_index != renderer->present_queue_family_index) {
// If the graphics and present queues are from different queue families,
// we either have to explicitly transfer ownership of images between
// the queues, or we have to create the swapchain with imageSharingMode
// as VK_SHARING_MODE_CONCURRENT
swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_info.queueFamilyIndexCount = 2;
swapchain_info.pQueueFamilyIndices = queue_family_indices;
// TODO @Speedup We may want optimize this by using VK_SHARING_MODE_EXCLUSIVE and be explicit about transfert ownership
}
res = vkCreateSwapchainKHR(renderer->device, &swapchain_info, nullptr, &renderer->swapchain);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the swapchain.n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Swapchain created with size (%d, %d).n",
swapchain_info.imageExtent.width,
swapchain_info.imageExtent.height);
get_enumeration(vkGetSwapchainImagesKHR,
VkImage,
swapchain_images,
"Failed to get swapchain images.n",
"Found %d swapchain images.n",
renderer->device,
renderer->swapchain);
renderer->swapchain_buffers.resize(swapchain_images.size());
for (uint32_t i = 0; i < swapchain_images.size(); i++) {
renderer->swapchain_buffers[i].image = swapchain_images[i];
}
for (uint32_t i = 0; i < swapchain_images.size(); i++) {
VkImageViewCreateInfo   color_image_view = {};
color_image_view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
color_image_view.pNext = nullptr;
color_image_view.flags = 0;
color_image_view.image = renderer->swapchain_buffers[i].image;
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
color_image_view.format = renderer->surface_format;
color_image_view.components.r = VK_COMPONENT_SWIZZLE_R;
color_image_view.components.g = VK_COMPONENT_SWIZZLE_G;
color_image_view.components.b = VK_COMPONENT_SWIZZLE_B;
color_image_view.components.a = VK_COMPONENT_SWIZZLE_A;
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
color_image_view.subresourceRange.baseMipLevel = 0;
color_image_view.subresourceRange.levelCount = 1;
color_image_view.subresourceRange.baseArrayLayer = 0;
color_image_view.subresourceRange.layerCount = 1;
res = vkCreateImageView(renderer->device, &color_image_view, nullptr, &renderer->swapchain_buffers[i].view);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create image view.n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Image view %d created.n", i);
}
// Build the depth buffer
VkImageCreateInfo   image_info = {};
const VkFormat      depth_format = VK_FORMAT_D32_SFLOAT;
VkFormatProperties  format_properties;
bool                found_memory_type_index;
vkGetPhysicalDeviceFormatProperties(renderer->physical_device, depth_format, &format_properties);
if (format_properties.linearTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_LINEAR;
} else if (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
} else {
// @TODO choose an other format?
log(globals.logger, Log_Level::error, "VK_FORMAT_D32_SFLOAT Unsupported.n");
clear_swapchain(renderer);
return false;
}
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = nullptr;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.format = depth_format;
image_info.extent.width = renderer->swapchain_extent.width;
image_info.extent.height = renderer->swapchain_extent.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.samples = renderer->sample_count_flag;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_info.queueFamilyIndexCount = 0;
image_info.pQueueFamilyIndices = nullptr;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.flags = 0;
VkMemoryAllocateInfo    memory_allocation_info = {};
memory_allocation_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocation_info.pNext = nullptr;
memory_allocation_info.allocationSize = 0;
memory_allocation_info.memoryTypeIndex = 0;
VkImageViewCreateInfo view_info = {};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.pNext = nullptr;
view_info.image = nullptr;
view_info.format = depth_format;
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
view_info.components.g = VK_COMPONENT_SWIZZLE_G;
view_info.components.b = VK_COMPONENT_SWIZZLE_B;
view_info.components.a = VK_COMPONENT_SWIZZLE_A;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.flags = 0;
VkMemoryRequirements    memory_requirements;
renderer->depth_buffer.format = depth_format;
/* Create image */
res = vkCreateImage(renderer->device, &image_info, nullptr, &renderer->depth_buffer.image);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the depth image.n");
clear_swapchain(renderer);
return false;
}
vkGetImageMemoryRequirements(renderer->device, renderer->depth_buffer.image, &memory_requirements);
memory_allocation_info.allocationSize = memory_requirements.size;
/* Use the memory properties to determine the type of memory required */
found_memory_type_index = memory_type_from_properties(renderer, memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memory_allocation_info.memoryTypeIndex);
if (!found_memory_type_index) {
log(globals.logger, Log_Level::error, "Failed to find memory type to allocate the depth image.n");
clear_swapchain(renderer);
return false;
}
/* Allocate memory */
res = vkAllocateMemory(renderer->device, &memory_allocation_info, nullptr, &renderer->depth_buffer.memory);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create memory for depth image.n");
clear_swapchain(renderer);
return false;
}
/* Bind memory */
res = vkBindImageMemory(renderer->device, renderer->depth_buffer.image, renderer->depth_buffer.memory, 0);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to bind the depth image memory.n");
clear_swapchain(renderer);
return false;
}
/* Create image view */
view_info.image = renderer->depth_buffer.image;
res = vkCreateImageView(renderer->device, &view_info, nullptr, &renderer->depth_buffer.view);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the depth image view.n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Depth buffer created.n");
for (size_t i = 0; i < renderer->scenes.size(); i++) {
swapchain_resized(renderer->scenes[i], renderer->swapchain_extent.width, renderer->swapchain_extent.height);
}
return true;
}

编辑:也许我的问题更多地与如何将渲染图像提交到swapchain或图像采集有关。

for (size_t i = 0; i < scene->meshes.size(); i++) {
draw_mesh(scene->meshes[i]);
}
// End the Render pass
vkCmdEndRenderPass(scene->renderer->graphical_command_buffer);
// End command buffer
{
res = vkEndCommandBuffer(scene->renderer->graphical_command_buffer);
}
// Execute queue command buffer
{
/* Queue the command buffer for execution */
const VkCommandBuffer   command_buffers[] = {scene->renderer->graphical_command_buffer};
VkFenceCreateInfo       fence_create_info;
VkFence                 draw_fence;
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_create_info.pNext = nullptr;
fence_create_info.flags = 0;
vkCreateFence(scene->renderer->device, &fence_create_info, nullptr, &draw_fence);
VkPipelineStageFlags    pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo            submit_info[1] = {};
submit_info[0].pNext = nullptr;
submit_info[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info[0].waitSemaphoreCount = 1;
submit_info[0].pWaitSemaphores = &scene->image_acquired_semaphore;
submit_info[0].pWaitDstStageMask = &pipe_stage_flags;
submit_info[0].commandBufferCount = 1;
submit_info[0].pCommandBuffers = command_buffers;
submit_info[0].signalSemaphoreCount = 0;
submit_info[0].pSignalSemaphores = nullptr;
/* Queue the command buffer for execution */
res = vkQueueSubmit(scene->renderer->graphics_queue, 1, submit_info, draw_fence);
assert(res == VK_SUCCESS);
/* Now present the image in the window */
VkPresentInfoKHR present;
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.pNext = nullptr;
present.swapchainCount = 1;
present.pSwapchains = &scene->renderer->swapchain;
present.pImageIndices = &scene->current_buffer;
present.pWaitSemaphores = nullptr;
present.waitSemaphoreCount = 0;
present.pResults = nullptr;
/* Make sure command buffer is finished before presenting */
do {
res = vkWaitForFences(scene->renderer->device, 1, &draw_fence, VK_TRUE, scene->renderer->draw_fence_timeout_us);
} while (res == VK_TIMEOUT);
assert(res == VK_SUCCESS);
res = vkQueuePresentKHR(scene->renderer->present_queue, &present);
assert(res == VK_SUCCESS);
vkDestroyFence(scene->renderer->device, draw_fence, nullptr);
}

在vulkan-tutorial.com上,我们还应该用swapchain重新创建命令缓冲区(https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation),它真的是强制性的吗?

我就是那个reddit线程的人。我不能100%确定我们是否有完全相同的问题,但我可以解释我在处理什么以及我是如何解决的。

因此,这里的问题有几个层面。第一个问题是,在Windows上调整窗口大小会阻塞消息队列,因为它需要为自己捕获所有输入事件。因此,要解决此问题,您需要使窗口更新异步,例如通过线程进行更新。

现在,渲染和窗口大小调整是异步工作的,这很好,直到有人在渲染新帧的中途调整窗口大小。这会立即导致交换链为VK_ERROR_OUT_OF_DATE_KHR,使您无法在屏幕上显示渲染结果。这可能会导致表面上出现各种不同的瑕疵,这取决于GPU供应商、驱动程序版本,甚至来自同一供应商的不同GPU之间。这实际上是一种未定义的行为。但闪烁肯定是常见的结果之一,在新的成功暂停之前,它根本不会在表面上显示任何东西。到目前为止,我还没有找到一个支持VK_SUBOPTIMAL_KHR的供应商来允许您继续渲染。

一个天真的解决方案是让窗口完全控制帧速率,但会给出非常差且不一致的帧定时,尤其是在超过60hz时。您希望渲染以尽可能快的速度运行,并尽可能减少延迟。

因此,在我进入解决方案之前,让我们总结一下需求:

  • 调整大小时,应用程序(包括渲染(不会冻结
  • 窗口不会在AcquirePresent之间调整大小
  • 帧定时不受窗口消息队列*的控制

*不调整大小时

你可能已经注意到了最后一个要求上的星号。这是因为我们将不得不作出一个小小的妥协。我们的想法是,我们只让窗口在调整大小时控制帧定时。除此之外,我们可以尽可能快地绘制,因为没有其他东西可以使中间的交换链无效。

为此,我使用了Fibers。你可以把纤维想象成一堆没有线的东西。然后,您可以从光纤跳到另一个光纤,然后再跳回来。还记得消息队列(特别是GetMessage/PeekMessage调用(在调整大小时不会返回吗?好吧,你可以跳出那个循环,重新使用纤维!与导致切换的计时器相结合,我们可以同步更新窗口和渲染帧。以下是我的代码示例:

LRESULT Window::Impl::WndProc(HWND a_HWND, UINT a_Message, WPARAM a_WParam, LPARAM a_LParam)
{
switch (a_Message)
{
case WM_ENTERSIZEMOVE:
SetTimer(a_HWND, 0, 1, NULL);
break;
case WM_EXITSIZEMOVE:
KillTimer(a_HWND, 0);
break;
case WM_TIMER:
m_MainFiber.Switch();
break;
case WM_MOVE:
if (m_MoveCallback)
{
m_MoveCallback(m_This, Vector2i(static_cast<int16_t>(LOWORD(a_LParam)), static_cast<int16_t>(HIWORD(a_LParam))));
}
break;
case WM_SIZE:
switch (a_WParam)
{
case SIZE_MINIMIZED:
if (m_MinimizeCallback)
{
m_MinimizeCallback(m_This);
}
break;
case SIZE_MAXIMIZED:
if (m_MaximizeCallback)
{
m_MaximizeCallback(m_This);
}
break;
}
if (m_ResizeCallback)
{
m_ResizeCallback(m_This, Vector2i(static_cast<int16_t>(LOWORD(a_LParam)), static_cast<int16_t>(HIWORD(a_LParam))));
}
break;
case WM_CLOSE:
if (m_CloseCallback)
{
m_CloseCallback(m_This);
}
break;
}
if (a_Message == WM_CLOSE)
{
return 0;
}
return DefWindowProcW(a_HWND, a_Message, a_WParam, a_LParam);
}

正如你所看到的,它实际上非常简单。调整大小开始时启动计时器,调整大小结束时停止计时器,触发时切换回原始光纤。

这是光纤回调本身:

void Window::Impl::FiberCallback()
{
MSG msg;
for (;;)
{
if (PeekMessageW(&msg, m_Window, 0, 0, PM_REMOVE) != 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else
{
m_MainFiber.Switch();
}
}
}

然后实际的轮询就这么简单:

void Window::PollEvents()
{
m_Impl->m_MessageFiber.Switch();
}

这将使PollEvents在不调整大小时始终立即返回,并且在调整大小时计时器到期后返回。它还完全避免了线程化,因为它都在同一个线程上运行,只是在堆栈之间切换。

如果有什么不清楚的地方,请留下评论,我希望它能解决你的问题。