duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

vulkan_swap_chain.cpp (25961B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "vulkan_swap_chain.h"
      5 #include "vulkan_builders.h"
      6 #include "vulkan_device.h"
      7 
      8 #include "common/assert.h"
      9 #include "common/log.h"
     10 
     11 #include <algorithm>
     12 #include <array>
     13 #include <cmath>
     14 
     15 #if defined(VK_USE_PLATFORM_XLIB_KHR)
     16 #include <X11/Xlib.h>
     17 #endif
     18 
     19 #if defined(VK_USE_PLATFORM_METAL_EXT)
     20 #include "util/metal_layer.h"
     21 #endif
     22 
     23 Log_SetChannel(VulkanDevice);
     24 
     25 static_assert(VulkanSwapChain::NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
     26 
     27 static VkFormat GetLinearFormat(VkFormat format)
     28 {
     29   switch (format)
     30   {
     31     case VK_FORMAT_R8_SRGB:
     32       return VK_FORMAT_R8_UNORM;
     33     case VK_FORMAT_R8G8_SRGB:
     34       return VK_FORMAT_R8G8_UNORM;
     35     case VK_FORMAT_R8G8B8_SRGB:
     36       return VK_FORMAT_R8G8B8_UNORM;
     37     case VK_FORMAT_R8G8B8A8_SRGB:
     38       return VK_FORMAT_R8G8B8A8_UNORM;
     39     case VK_FORMAT_B8G8R8_SRGB:
     40       return VK_FORMAT_B8G8R8_UNORM;
     41     case VK_FORMAT_B8G8R8A8_SRGB:
     42       return VK_FORMAT_B8G8R8A8_UNORM;
     43     default:
     44       return format;
     45   }
     46 }
     47 
     48 static const char* PresentModeToString(VkPresentModeKHR mode)
     49 {
     50   switch (mode)
     51   {
     52     case VK_PRESENT_MODE_IMMEDIATE_KHR:
     53       return "VK_PRESENT_MODE_IMMEDIATE_KHR";
     54 
     55     case VK_PRESENT_MODE_MAILBOX_KHR:
     56       return "VK_PRESENT_MODE_MAILBOX_KHR";
     57 
     58     case VK_PRESENT_MODE_FIFO_KHR:
     59       return "VK_PRESENT_MODE_FIFO_KHR";
     60 
     61     case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
     62       return "VK_PRESENT_MODE_FIFO_RELAXED_KHR";
     63 
     64     case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
     65       return "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR";
     66 
     67     case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
     68       return "VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR";
     69 
     70     default:
     71       return "UNKNOWN_VK_PRESENT_MODE";
     72   }
     73 }
     74 
     75 VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
     76                                  std::optional<bool> exclusive_fullscreen_control)
     77   : m_window_info(wi), m_surface(surface), m_present_mode(present_mode),
     78     m_exclusive_fullscreen_control(exclusive_fullscreen_control)
     79 {
     80 }
     81 
     82 VulkanSwapChain::~VulkanSwapChain()
     83 {
     84   DestroySwapChainImages();
     85   DestroySwapChain();
     86   DestroySurface();
     87 }
     88 
     89 VkSurfaceKHR VulkanSwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi)
     90 {
     91 #if defined(VK_USE_PLATFORM_WIN32_KHR)
     92   if (wi->type == WindowInfo::Type::Win32)
     93   {
     94     VkWin32SurfaceCreateInfoKHR surface_create_info = {
     95       VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType               sType
     96       nullptr,                                         // const void*                   pNext
     97       0,                                               // VkWin32SurfaceCreateFlagsKHR  flags
     98       nullptr,                                         // HINSTANCE                     hinstance
     99       reinterpret_cast<HWND>(wi->window_handle)        // HWND                          hwnd
    100     };
    101 
    102     VkSurfaceKHR surface;
    103     VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface);
    104     if (res != VK_SUCCESS)
    105     {
    106       LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: ");
    107       return VK_NULL_HANDLE;
    108     }
    109 
    110     return surface;
    111   }
    112 #endif
    113 
    114 #if defined(VK_USE_PLATFORM_METAL_EXT)
    115   if (wi->type == WindowInfo::Type::MacOS)
    116   {
    117     // TODO: FIXME
    118     if (!wi->surface_handle && !CocoaTools::CreateMetalLayer(wi))
    119       return VK_NULL_HANDLE;
    120 
    121     VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0,
    122                                                        static_cast<const CAMetalLayer*>(wi->surface_handle)};
    123 
    124     VkSurfaceKHR surface;
    125     VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface);
    126     if (res != VK_SUCCESS)
    127     {
    128       LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: ");
    129       return VK_NULL_HANDLE;
    130     }
    131 
    132     return surface;
    133   }
    134 #endif
    135 
    136 #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    137   if (wi->type == WindowInfo::Type::Android)
    138   {
    139     VkAndroidSurfaceCreateInfoKHR surface_create_info = {
    140       VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,  // VkStructureType                sType
    141       nullptr,                                            // const void*                    pNext
    142       0,                                                  // VkAndroidSurfaceCreateFlagsKHR flags
    143       reinterpret_cast<ANativeWindow*>(wi->window_handle) // ANativeWindow* window
    144     };
    145 
    146     VkSurfaceKHR surface;
    147     VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
    148     if (res != VK_SUCCESS)
    149     {
    150       LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: ");
    151       return VK_NULL_HANDLE;
    152     }
    153 
    154     return surface;
    155   }
    156 #endif
    157 
    158 #if defined(VK_USE_PLATFORM_XLIB_KHR)
    159   if (wi->type == WindowInfo::Type::X11)
    160   {
    161     VkXlibSurfaceCreateInfoKHR surface_create_info = {
    162       VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType               sType
    163       nullptr,                                        // const void*                   pNext
    164       0,                                              // VkXlibSurfaceCreateFlagsKHR   flags
    165       static_cast<Display*>(wi->display_connection),  // Display*                      dpy
    166       reinterpret_cast<Window>(wi->window_handle)     // Window                        window
    167     };
    168 
    169     VkSurfaceKHR surface;
    170     VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
    171     if (res != VK_SUCCESS)
    172     {
    173       LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: ");
    174       return VK_NULL_HANDLE;
    175     }
    176 
    177     return surface;
    178   }
    179 #endif
    180 
    181 #if defined(VK_USE_PLATFORM_WAYLAND_KHR)
    182   if (wi->type == WindowInfo::Type::Wayland)
    183   {
    184     VkWaylandSurfaceCreateInfoKHR surface_create_info = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
    185                                                          static_cast<struct wl_display*>(wi->display_connection),
    186                                                          static_cast<struct wl_surface*>(wi->window_handle)};
    187 
    188     VkSurfaceKHR surface;
    189     VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
    190     if (res != VK_SUCCESS)
    191     {
    192       LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceEXT failed: ");
    193       return VK_NULL_HANDLE;
    194     }
    195 
    196     return surface;
    197   }
    198 #endif
    199 
    200   return VK_NULL_HANDLE;
    201 }
    202 
    203 void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface)
    204 {
    205   vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), surface, nullptr);
    206 
    207 #if defined(__APPLE__)
    208   if (wi->type == WindowInfo::Type::MacOS && wi->surface_handle)
    209     CocoaTools::DestroyMetalLayer(wi);
    210 #endif
    211 }
    212 
    213 std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
    214                                                          VkPresentModeKHR present_mode,
    215                                                          std::optional<bool> exclusive_fullscreen_control)
    216 {
    217   std::unique_ptr<VulkanSwapChain> swap_chain =
    218     std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, present_mode, exclusive_fullscreen_control));
    219   if (!swap_chain->CreateSwapChain())
    220     return nullptr;
    221 
    222   return swap_chain;
    223 }
    224 
    225 std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface)
    226 {
    227   VulkanDevice& dev = VulkanDevice::GetInstance();
    228   u32 format_count;
    229   VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, nullptr);
    230   if (res != VK_SUCCESS || format_count == 0)
    231   {
    232     LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
    233     return std::nullopt;
    234   }
    235 
    236   std::vector<VkSurfaceFormatKHR> surface_formats(format_count);
    237   res =
    238     vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, surface_formats.data());
    239   Assert(res == VK_SUCCESS);
    240 
    241   // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA
    242   const auto has_format = [&surface_formats](VkFormat fmt) {
    243     return std::any_of(surface_formats.begin(), surface_formats.end(), [fmt](const VkSurfaceFormatKHR& sf) {
    244       return (sf.format == fmt || GetLinearFormat(sf.format) == fmt);
    245     });
    246   };
    247   if (has_format(VK_FORMAT_UNDEFINED))
    248     return VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
    249 
    250   // Prefer 8-bit formats.
    251   for (VkFormat format : {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R5G6B5_UNORM_PACK16,
    252                           VK_FORMAT_R5G5B5A1_UNORM_PACK16})
    253   {
    254     if (has_format(format))
    255       return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
    256   }
    257 
    258   ERROR_LOG("Failed to find a suitable format for swap chain buffers. Available formats were:");
    259   for (const VkSurfaceFormatKHR& sf : surface_formats)
    260     ERROR_LOG("  {}", static_cast<unsigned>(sf.format));
    261 
    262   return std::nullopt;
    263 }
    264 
    265 bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode)
    266 {
    267   VulkanDevice& dev = VulkanDevice::GetInstance();
    268   VkResult res;
    269   u32 mode_count;
    270   res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count, nullptr);
    271   if (res != VK_SUCCESS || mode_count == 0)
    272   {
    273     LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
    274     return false;
    275   }
    276 
    277   std::vector<VkPresentModeKHR> present_modes(mode_count);
    278   res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count,
    279                                                   present_modes.data());
    280   Assert(res == VK_SUCCESS);
    281 
    282   // Checks if a particular mode is supported, if it is, returns that mode.
    283   const auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) {
    284     auto it = std::find_if(present_modes.begin(), present_modes.end(),
    285                            [check_mode](VkPresentModeKHR mode) { return check_mode == mode; });
    286     return it != present_modes.end();
    287   };
    288 
    289   switch (*vsync_mode)
    290   {
    291     case GPUVSyncMode::Disabled:
    292     {
    293       // Prefer immediate > mailbox > fifo.
    294       if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR))
    295       {
    296         *present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
    297       }
    298       else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
    299       {
    300         WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox.");
    301         *present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
    302       }
    303       else
    304       {
    305         WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO.");
    306         *present_mode = VK_PRESENT_MODE_FIFO_KHR;
    307         *vsync_mode = GPUVSyncMode::FIFO;
    308       }
    309     }
    310     break;
    311 
    312     case GPUVSyncMode::FIFO:
    313     {
    314       // FIFO is always available.
    315       *present_mode = VK_PRESENT_MODE_FIFO_KHR;
    316     }
    317     break;
    318 
    319     case GPUVSyncMode::Mailbox:
    320     {
    321       // Mailbox > fifo.
    322       if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
    323       {
    324         *present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
    325       }
    326       else
    327       {
    328         WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO.");
    329         *present_mode = VK_PRESENT_MODE_FIFO_KHR;
    330         *vsync_mode = GPUVSyncMode::FIFO;
    331       }
    332     }
    333     break;
    334 
    335       DefaultCaseIsUnreachable()
    336   }
    337 
    338   return true;
    339 }
    340 
    341 bool VulkanSwapChain::CreateSwapChain()
    342 {
    343   VulkanDevice& dev = VulkanDevice::GetInstance();
    344 
    345   // Select swap chain format
    346   std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
    347   if (!surface_format.has_value())
    348     return false;
    349 
    350   // Look up surface properties to determine image count and dimensions
    351   VkSurfaceCapabilitiesKHR surface_capabilities;
    352   VkResult res =
    353     vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.GetVulkanPhysicalDevice(), m_surface, &surface_capabilities);
    354   if (res != VK_SUCCESS)
    355   {
    356     LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ");
    357     return false;
    358   }
    359 
    360   // Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode.
    361   // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
    362   u32 image_count = std::clamp<u32>(
    363     (m_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
    364     (surface_capabilities.maxImageCount == 0) ? std::numeric_limits<u32>::max() : surface_capabilities.maxImageCount);
    365   DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, PresentModeToString(m_present_mode));
    366 
    367   // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
    368   // determines window size? Android sometimes lags updating currentExtent, so don't use it.
    369   VkExtent2D size = surface_capabilities.currentExtent;
    370 #ifndef __ANDROID__
    371   if (size.width == UINT32_MAX)
    372 #endif
    373   {
    374     size.width = m_window_info.surface_width;
    375     size.height = m_window_info.surface_height;
    376   }
    377   size.width =
    378     std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
    379   size.height =
    380     std::clamp(size.height, surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
    381 
    382   // Prefer identity transform if possible
    383   VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
    384   if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
    385     transform = surface_capabilities.currentTransform;
    386 
    387   VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    388   if (!(surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR))
    389   {
    390     // If we only support pre-multiplied/post-multiplied... :/
    391     if (surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
    392       alpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
    393   }
    394 
    395   // Select swap chain flags, we only need a colour attachment
    396   VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    397   if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage)
    398   {
    399     ERROR_LOG("Vulkan: Swap chain does not support usage as color attachment");
    400     return false;
    401   }
    402 
    403   // Store the old/current swap chain when recreating for resize
    404   // Old swap chain is destroyed regardless of whether the create call succeeds
    405   VkSwapchainKHR old_swap_chain = m_swap_chain;
    406   m_swap_chain = VK_NULL_HANDLE;
    407 
    408   // Now we can actually create the swap chain
    409   VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
    410                                               nullptr,
    411                                               0,
    412                                               m_surface,
    413                                               image_count,
    414                                               surface_format->format,
    415                                               surface_format->colorSpace,
    416                                               size,
    417                                               1u,
    418                                               image_usage,
    419                                               VK_SHARING_MODE_EXCLUSIVE,
    420                                               0,
    421                                               nullptr,
    422                                               transform,
    423                                               alpha,
    424                                               m_present_mode,
    425                                               VK_TRUE,
    426                                               old_swap_chain};
    427   std::array<uint32_t, 2> indices = {{
    428     dev.GetGraphicsQueueFamilyIndex(),
    429     dev.GetPresentQueueFamilyIndex(),
    430   }};
    431   if (dev.GetGraphicsQueueFamilyIndex() != dev.GetPresentQueueFamilyIndex())
    432   {
    433     swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    434     swap_chain_info.queueFamilyIndexCount = 2;
    435     swap_chain_info.pQueueFamilyIndices = indices.data();
    436   }
    437 
    438 #ifdef _WIN32
    439   VkSurfaceFullScreenExclusiveInfoEXT exclusive_info = {VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT,
    440                                                         nullptr, VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT};
    441   VkSurfaceFullScreenExclusiveWin32InfoEXT exclusive_win32_info = {
    442     VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT, nullptr, NULL};
    443   if (m_exclusive_fullscreen_control.has_value())
    444   {
    445     if (dev.GetOptionalExtensions().vk_ext_full_screen_exclusive)
    446     {
    447       exclusive_info.fullScreenExclusive =
    448         (m_exclusive_fullscreen_control.value() ? VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT :
    449                                                   VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT);
    450 
    451       exclusive_win32_info.hmonitor =
    452         MonitorFromWindow(reinterpret_cast<HWND>(m_window_info.window_handle), MONITOR_DEFAULTTONEAREST);
    453       if (!exclusive_win32_info.hmonitor)
    454         ERROR_LOG("MonitorFromWindow() for exclusive fullscreen exclusive override failed.");
    455 
    456       Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_info);
    457       Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_win32_info);
    458     }
    459     else
    460     {
    461       ERROR_LOG("Exclusive fullscreen control requested, but VK_EXT_full_screen_exclusive is not supported.");
    462     }
    463   }
    464 #else
    465   if (m_exclusive_fullscreen_control.has_value())
    466     ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform.");
    467 #endif
    468 
    469   res = vkCreateSwapchainKHR(dev.GetVulkanDevice(), &swap_chain_info, nullptr, &m_swap_chain);
    470   if (res != VK_SUCCESS)
    471   {
    472     LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: ");
    473     return false;
    474   }
    475 
    476   // Now destroy the old swap chain, since it's been recreated.
    477   // We can do this immediately since all work should have been completed before calling resize.
    478   if (old_swap_chain != VK_NULL_HANDLE)
    479     vkDestroySwapchainKHR(dev.GetVulkanDevice(), old_swap_chain, nullptr);
    480 
    481   m_format = surface_format->format;
    482   m_window_info.surface_width = std::max(1u, size.width);
    483   m_window_info.surface_height = std::max(1u, size.height);
    484   m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
    485   if (m_window_info.surface_format == GPUTexture::Format::Unknown)
    486   {
    487     ERROR_LOG("Unknown Vulkan surface format {}", static_cast<u32>(surface_format->format));
    488     return false;
    489   }
    490 
    491   // Get and create images.
    492   Assert(m_images.empty());
    493 
    494   res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, nullptr);
    495   if (res != VK_SUCCESS)
    496   {
    497     LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: ");
    498     return false;
    499   }
    500 
    501   std::vector<VkImage> images(image_count);
    502   res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, images.data());
    503   Assert(res == VK_SUCCESS);
    504 
    505   VkRenderPass render_pass = dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
    506   if (render_pass == VK_NULL_HANDLE)
    507     return false;
    508 
    509   Vulkan::FramebufferBuilder fbb;
    510   m_images.reserve(image_count);
    511   m_current_image = 0;
    512   for (u32 i = 0; i < image_count; i++)
    513   {
    514     Image image = {};
    515     image.image = images[i];
    516 
    517     const VkImageViewCreateInfo view_info = {
    518       VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
    519       nullptr,
    520       0,
    521       images[i],
    522       VK_IMAGE_VIEW_TYPE_2D,
    523       m_format,
    524       {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
    525        VK_COMPONENT_SWIZZLE_IDENTITY},
    526       {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u},
    527     };
    528     if ((res = vkCreateImageView(dev.GetVulkanDevice(), &view_info, nullptr, &image.view)) != VK_SUCCESS)
    529     {
    530       LOG_VULKAN_ERROR(res, "vkCreateImageView() failed: ");
    531       return false;
    532     }
    533 
    534     fbb.AddAttachment(image.view);
    535     fbb.SetRenderPass(render_pass);
    536     fbb.SetSize(size.width, size.height, 1);
    537     if ((image.framebuffer = fbb.Create(dev.GetVulkanDevice())) == VK_NULL_HANDLE)
    538     {
    539       vkDestroyImageView(dev.GetVulkanDevice(), image.view, nullptr);
    540       return false;
    541     }
    542 
    543     m_images.push_back(image);
    544   }
    545 
    546   m_current_semaphore = 0;
    547   for (u32 i = 0; i < NUM_SEMAPHORES; i++)
    548   {
    549     ImageSemaphores& sema = m_semaphores[i];
    550 
    551     const VkSemaphoreCreateInfo semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
    552     res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.available_semaphore);
    553     if (res != VK_SUCCESS)
    554     {
    555       LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
    556       return false;
    557     }
    558 
    559     res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.rendering_finished_semaphore);
    560     if (res != VK_SUCCESS)
    561     {
    562       LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
    563       vkDestroySemaphore(dev.GetVulkanDevice(), sema.available_semaphore, nullptr);
    564       sema.available_semaphore = VK_NULL_HANDLE;
    565       return false;
    566     }
    567   }
    568 
    569   return true;
    570 }
    571 
    572 void VulkanSwapChain::DestroySwapChainImages()
    573 {
    574   VulkanDevice& dev = VulkanDevice::GetInstance();
    575   for (const auto& it : m_images)
    576   {
    577     // don't defer view destruction, images are no longer valid
    578     vkDestroyFramebuffer(dev.GetVulkanDevice(), it.framebuffer, nullptr);
    579     vkDestroyImageView(dev.GetVulkanDevice(), it.view, nullptr);
    580   }
    581   m_images.clear();
    582   for (auto& it : m_semaphores)
    583   {
    584     if (it.rendering_finished_semaphore != VK_NULL_HANDLE)
    585       vkDestroySemaphore(dev.GetVulkanDevice(), it.rendering_finished_semaphore, nullptr);
    586     if (it.available_semaphore != VK_NULL_HANDLE)
    587       vkDestroySemaphore(dev.GetVulkanDevice(), it.available_semaphore, nullptr);
    588   }
    589   m_semaphores = {};
    590 
    591   m_image_acquire_result.reset();
    592 }
    593 
    594 void VulkanSwapChain::DestroySwapChain()
    595 {
    596   DestroySwapChainImages();
    597 
    598   if (m_swap_chain == VK_NULL_HANDLE)
    599     return;
    600 
    601   vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr);
    602   m_swap_chain = VK_NULL_HANDLE;
    603   m_window_info.surface_width = 0;
    604   m_window_info.surface_height = 0;
    605 }
    606 
    607 VkResult VulkanSwapChain::AcquireNextImage()
    608 {
    609   if (m_image_acquire_result.has_value())
    610     return m_image_acquire_result.value();
    611 
    612   if (!m_swap_chain)
    613     return VK_ERROR_SURFACE_LOST_KHR;
    614 
    615   // Use a different semaphore for each image.
    616   m_current_semaphore = (m_current_semaphore + 1) % static_cast<u32>(m_semaphores.size());
    617 
    618   const VkResult res =
    619     vkAcquireNextImageKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, UINT64_MAX,
    620                           m_semaphores[m_current_semaphore].available_semaphore, VK_NULL_HANDLE, &m_current_image);
    621   m_image_acquire_result = res;
    622   return res;
    623 }
    624 
    625 void VulkanSwapChain::ReleaseCurrentImage()
    626 {
    627   if (!m_image_acquire_result.has_value())
    628     return;
    629 
    630   if ((m_image_acquire_result.value() == VK_SUCCESS || m_image_acquire_result.value() == VK_SUBOPTIMAL_KHR) &&
    631       VulkanDevice::GetInstance().GetOptionalExtensions().vk_ext_swapchain_maintenance1)
    632   {
    633     VulkanDevice::GetInstance().WaitForGPUIdle();
    634 
    635     const VkReleaseSwapchainImagesInfoEXT info = {.sType = VK_STRUCTURE_TYPE_RELEASE_SWAPCHAIN_IMAGES_INFO_EXT,
    636                                                   .swapchain = m_swap_chain,
    637                                                   .imageIndexCount = 1,
    638                                                   .pImageIndices = &m_current_image};
    639     VkResult res = vkReleaseSwapchainImagesEXT(VulkanDevice::GetInstance().GetVulkanDevice(), &info);
    640     if (res != VK_SUCCESS)
    641       LOG_VULKAN_ERROR(res, "vkReleaseSwapchainImagesEXT() failed: ");
    642   }
    643 
    644   m_image_acquire_result.reset();
    645 }
    646 
    647 void VulkanSwapChain::ResetImageAcquireResult()
    648 {
    649   m_image_acquire_result.reset();
    650 }
    651 
    652 bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_scale)
    653 {
    654   ReleaseCurrentImage();
    655   DestroySwapChainImages();
    656 
    657   if (new_width != 0 && new_height != 0)
    658   {
    659     m_window_info.surface_width = new_width;
    660     m_window_info.surface_height = new_height;
    661   }
    662 
    663   m_window_info.surface_scale = new_scale;
    664 
    665   if (!CreateSwapChain())
    666   {
    667     DestroySwapChain();
    668     return false;
    669   }
    670 
    671   return true;
    672 }
    673 
    674 bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
    675 {
    676   if (m_present_mode == present_mode)
    677     return true;
    678 
    679   m_present_mode = present_mode;
    680 
    681   // Recreate the swap chain with the new present mode.
    682   VERBOSE_LOG("Recreating swap chain to change present mode.");
    683   ReleaseCurrentImage();
    684   DestroySwapChainImages();
    685   if (!CreateSwapChain())
    686   {
    687     DestroySwapChain();
    688     return false;
    689   }
    690 
    691   return true;
    692 }
    693 
    694 bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
    695 {
    696   VulkanDevice& dev = VulkanDevice::GetInstance();
    697 
    698   // Destroy the old swap chain, images, and surface.
    699   DestroySwapChain();
    700   DestroySurface();
    701 
    702   // Re-create the surface with the new native handle
    703   m_window_info = new_wi;
    704   m_surface = CreateVulkanSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), &m_window_info);
    705   if (m_surface == VK_NULL_HANDLE)
    706     return false;
    707 
    708   // The validation layers get angry at us if we don't call this before creating the swapchain.
    709   VkBool32 present_supported = VK_TRUE;
    710   VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(dev.GetVulkanPhysicalDevice(), dev.GetPresentQueueFamilyIndex(),
    711                                                       m_surface, &present_supported);
    712   if (res != VK_SUCCESS)
    713   {
    714     LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
    715     return false;
    716   }
    717   if (!present_supported)
    718   {
    719     Panic("Recreated surface does not support presenting.");
    720     return false;
    721   }
    722 
    723   // Finally re-create the swap chain
    724   if (!CreateSwapChain())
    725   {
    726     DestroySwapChain();
    727     return false;
    728   }
    729 
    730   return true;
    731 }
    732 
    733 void VulkanSwapChain::DestroySurface()
    734 {
    735   if (m_surface == VK_NULL_HANDLE)
    736     return;
    737 
    738   DestroyVulkanSurface(VulkanDevice::GetInstance().GetVulkanInstance(), &m_window_info, m_surface);
    739   m_surface = VK_NULL_HANDLE;
    740 }