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

调整交换链图像的大小时(由于窗口大小已更改),我出现一些白色闪烁。我真的不明白为什么是这个问题的根源。 仅在将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->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,"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,&renderer->swapchain_buffers[i].view);
        if (res != VK_SUCCESS) {
            log(globals.logger,"Failed to create image view.\n");
            clear_swapchain(renderer);
            return false;
        }
        log(globals.logger,"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,"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,&renderer->depth_buffer.image);
    if (res != VK_SUCCESS) {
        log(globals.logger,"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,"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,&renderer->depth_buffer.memory);
    if (res != VK_SUCCESS) {
        log(globals.logger,"Failed to create memory for depth image.\n");
        clear_swapchain(renderer);
        return false;
    }

    /* Bind memory */
    res = vkBindImageMemory(renderer->device,renderer->depth_buffer.memory,0);
    if (res != VK_SUCCESS) {
        log(globals.logger,"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,&renderer->depth_buffer.view);
    if (res != VK_SUCCESS) {
        log(globals.logger,"Failed to create the depth image view.\n");
        clear_swapchain(renderer);
        return false;
    }

    log(globals.logger,"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;
}

编辑:也许我的问题与我如何将渲染图像提交到交换链或图像获取有关。

    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,&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,&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)重新创建命令缓冲区,这真的是强制性的吗?

ssssf 回答:在使用VK_PRESENT_MODE_FIFO_KHR当前模式调整大小时,为什么在调整交换链的图像大小时出现白色闪烁?

我是那个reddit线程中的人。我不确定100%是否存在完全相同的问题,但是我可以解释我正在处理的内容以及如何解决该问题。

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

现在,渲染和窗口调整大小是异步进行的,这很棒,直到在渲染新框架的过程中有人调整窗口的大小为止。此立即导致交换链为VK_ERROR_OUT_OF_DATE_KHR,使您无法在屏幕上呈现渲染结果。这可能会在表面上导致各种不同的工件,这取决于GPU供应商,驱动程序版本,甚至取决于同一供应商的不同GPU之间。这实际上是未定义的行为。但是闪烁肯定是常见的结果之一,在这种情况下,直到新的成功暂停,它才在表面上什么都没有显示。到目前为止,我还没有找到一家支持VK_SUBOPTIMAL_KHR来允许您继续渲染的供应商。

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

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

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

*不调整大小时

您可能已经注意到最后一个要求上的星号。这是因为我们将不得不做出一些小的妥协。这个想法是,我们只允许窗口在调整大小时控制帧时序。除此之外,我们可以尽可能快地进行绘制,因为没有别的方法可以使两者之间的交换链无效。

为此,我使用了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,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,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,PM_REMOVE) != 0)
        {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
        else
        {
            m_MainFiber.Switch();
        }
    }
}

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

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

这应该使PollEvents总是在不调整大小时立即返回,并且在定时器到期时重新调整时总是返回。它还完全避免线程化,因为它们都在同一线程上运行,它只是在堆栈之间切换。

如果不清楚,请发表评论,希望它能解决您的问题。

本文链接:https://www.f2er.com/3121416.html

大家都在问