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 }