// Copyright 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoBackends/Vulkan/VKSwapChain.h" #include #include #include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/Present.h" #if defined(VK_USE_PLATFORM_XLIB_KHR) #include #endif namespace Vulkan { SwapChain::SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync) : m_wsi(wsi), m_surface(surface), m_vsync_enabled(vsync), m_fullscreen_supported(g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface)) { } SwapChain::~SwapChain() { DestroySwapChainImages(); DestroySwapChain(); DestroySurface(); } VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi) { #if defined(VK_USE_PLATFORM_WIN32_KHR) if (wsi.type == WindowSystemType::Windows) { VkWin32SurfaceCreateInfoKHR surface_create_info = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType nullptr, // const void* pNext 0, // VkWin32SurfaceCreateFlagsKHR flags nullptr, // HINSTANCE hinstance reinterpret_cast(wsi.render_surface) // HWND hwnd }; VkSurfaceKHR surface; VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: "); return VK_NULL_HANDLE; } return surface; } #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) if (wsi.type == WindowSystemType::X11) { VkXlibSurfaceCreateInfoKHR surface_create_info = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType nullptr, // const void* pNext 0, // VkXlibSurfaceCreateFlagsKHR flags static_cast(wsi.display_connection), // Display* dpy reinterpret_cast(wsi.render_surface) // Window window }; VkSurfaceKHR surface; VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: "); return VK_NULL_HANDLE; } return surface; } #endif #if defined(VK_USE_PLATFORM_ANDROID_KHR) if (wsi.type == WindowSystemType::Android) { VkAndroidSurfaceCreateInfoKHR surface_create_info = { VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType nullptr, // const void* pNext 0, // VkAndroidSurfaceCreateFlagsKHR flags reinterpret_cast(wsi.render_surface) // ANativeWindow* window }; VkSurfaceKHR surface; VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: "); return VK_NULL_HANDLE; } return surface; } #endif #if defined(VK_USE_PLATFORM_METAL_EXT) if (wsi.type == WindowSystemType::MacOS) { VkMetalSurfaceCreateInfoEXT surface_create_info = { VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0, static_cast(wsi.render_surface)}; VkSurfaceKHR surface; VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: "); return VK_NULL_HANDLE; } return surface; } #endif return VK_NULL_HANDLE; } std::unique_ptr SwapChain::Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync) { std::unique_ptr swap_chain = std::make_unique(wsi, surface, vsync); if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages()) return nullptr; return swap_chain; } bool SwapChain::SelectSurfaceFormat() { u32 format_count; VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &format_count, nullptr); if (res != VK_SUCCESS || format_count == 0) { LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); return false; } std::vector surface_formats(format_count); res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &format_count, surface_formats.data()); ASSERT(res == VK_SUCCESS); // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA if (surface_formats[0].format == VK_FORMAT_UNDEFINED) { m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; m_surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; return true; } // Try to find a suitable format. for (const VkSurfaceFormatKHR& surface_format : surface_formats) { // Some drivers seem to return a SRGB format here (Intel Mesa). // This results in gamma correction when presenting to the screen, which we don't want. // Use a linear format instead, if this is the case. VkFormat format = VKTexture::GetLinearFormat(surface_format.format); if (format == VK_FORMAT_R8G8B8A8_UNORM) m_texture_format = AbstractTextureFormat::RGBA8; else if (format == VK_FORMAT_B8G8R8A8_UNORM) m_texture_format = AbstractTextureFormat::BGRA8; else continue; m_surface_format.format = format; m_surface_format.colorSpace = surface_format.colorSpace; return true; } PanicAlertFmt("Failed to find a suitable format for swap chain buffers."); return false; } bool SwapChain::SelectPresentMode() { VkResult res; u32 mode_count; res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &mode_count, nullptr); if (res != VK_SUCCESS || mode_count == 0) { LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); return false; } std::vector present_modes(mode_count); res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &mode_count, present_modes.data()); ASSERT(res == VK_SUCCESS); // Checks if a particular mode is supported, if it is, returns that mode. auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { auto it = std::find_if(present_modes.begin(), present_modes.end(), [check_mode](VkPresentModeKHR mode) { return check_mode == mode; }); return it != present_modes.end(); }; // If vsync is enabled, use VK_PRESENT_MODE_FIFO_KHR. // This check should not fail with conforming drivers, as the FIFO present mode is mandated by // the specification (VK_KHR_swapchain). In case it isn't though, fall through to any other mode. if (m_vsync_enabled && CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) { m_present_mode = VK_PRESENT_MODE_FIFO_KHR; return true; } // Prefer screen-tearing, if possible, for lowest latency. if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) { m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; return true; } // Use optimized-vsync above vsync. if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) { m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; return true; } // Fall back to whatever is available. m_present_mode = present_modes[0]; return true; } bool SwapChain::CreateSwapChain() { // Look up surface properties to determine image count and dimensions VkSurfaceCapabilitiesKHR surface_capabilities; VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &surface_capabilities); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: "); return false; } // Select swap chain format and present mode if (!SelectSurfaceFormat() || !SelectPresentMode()) return false; // Select number of images in swap chain, we prefer one buffer in the background to work on uint32_t image_count = surface_capabilities.minImageCount + 1; // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers. if (surface_capabilities.maxImageCount > 0) image_count = std::min(image_count, surface_capabilities.maxImageCount); // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here // determines window size? VkExtent2D size = surface_capabilities.currentExtent; if (size.width == UINT32_MAX) { size.width = std::max(g_presenter->GetBackbufferWidth(), 1); size.height = std::max(g_presenter->GetBackbufferHeight(), 1); } size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width); size.height = std::clamp(size.height, surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height); // Prefer identity transform if possible VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) transform = surface_capabilities.currentTransform; // Select swap chain flags, we only need a colour attachment VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) { ERROR_LOG_FMT(VIDEO, "Vulkan: Swap chain does not support usage as color attachment"); return false; } // Select the number of image layers for Quad-Buffered stereoscopy uint32_t image_layers = g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer ? 2 : 1; // Store the old/current swap chain when recreating for resize VkSwapchainKHR old_swap_chain = m_swap_chain; m_swap_chain = VK_NULL_HANDLE; // Now we can actually create the swap chain VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, nullptr, 0, m_surface, image_count, m_surface_format.format, m_surface_format.colorSpace, size, image_layers, image_usage, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, transform, VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, m_present_mode, VK_TRUE, old_swap_chain}; std::array indices = {{ g_vulkan_context->GetGraphicsQueueFamilyIndex(), g_vulkan_context->GetPresentQueueFamilyIndex(), }}; if (g_vulkan_context->GetGraphicsQueueFamilyIndex() != g_vulkan_context->GetPresentQueueFamilyIndex()) { swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swap_chain_info.queueFamilyIndexCount = 2; swap_chain_info.pQueueFamilyIndices = indices.data(); } #ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN if (m_fullscreen_supported) { VkSurfaceFullScreenExclusiveInfoEXT fullscreen_support = {}; swap_chain_info.pNext = &fullscreen_support; fullscreen_support.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT; fullscreen_support.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT; auto platform_info = g_vulkan_context->GetPlatformExclusiveFullscreenInfo(m_wsi); fullscreen_support.pNext = &platform_info; res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, &m_swap_chain); if (res != VK_SUCCESS) { // Try without exclusive fullscreen. WARN_LOG_FMT(VIDEO, "Failed to create exclusive fullscreen swapchain, trying without."); swap_chain_info.pNext = nullptr; g_Config.backend_info.bSupportsExclusiveFullscreen = false; g_ActiveConfig.backend_info.bSupportsExclusiveFullscreen = false; m_fullscreen_supported = false; } } #endif if (m_swap_chain == VK_NULL_HANDLE) { res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, &m_swap_chain); } if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); return false; } // Now destroy the old swap chain, since it's been recreated. // We can do this immediately since all work should have been completed before calling resize. if (old_swap_chain != VK_NULL_HANDLE) vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr); m_width = size.width; m_height = size.height; m_layers = image_layers; return true; } bool SwapChain::SetupSwapChainImages() { ASSERT(m_swap_chain_images.empty()); uint32_t image_count; VkResult res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, nullptr); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); return false; } std::vector images(image_count); res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, images.data()); ASSERT(res == VK_SUCCESS); const TextureConfig texture_config(TextureConfig( m_width, m_height, 1, m_layers, 1, m_texture_format, AbstractTextureFlag_RenderTarget)); const VkRenderPass load_render_pass = g_object_cache->GetRenderPass( m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_LOAD); const VkRenderPass clear_render_pass = g_object_cache->GetRenderPass( m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_CLEAR); if (load_render_pass == VK_NULL_HANDLE || clear_render_pass == VK_NULL_HANDLE) { PanicAlertFmt("Failed to get swap chain render passes."); return false; } m_swap_chain_images.reserve(image_count); for (uint32_t i = 0; i < image_count; i++) { SwapChainImage image; image.image = images[i]; // Create texture object, which creates a view of the backbuffer image.texture = VKTexture::CreateAdopted(texture_config, image.image, m_layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_LAYOUT_UNDEFINED); if (!image.texture) return false; image.framebuffer = VKFramebuffer::Create(image.texture.get(), nullptr); if (!image.framebuffer) { image.texture.reset(); return false; } m_swap_chain_images.emplace_back(std::move(image)); } return true; } void SwapChain::DestroySwapChainImages() { for (auto& it : m_swap_chain_images) { // Images themselves are cleaned up by the swap chain object it.framebuffer.reset(); it.texture.reset(); } m_swap_chain_images.clear(); } void SwapChain::DestroySwapChain() { if (m_swap_chain == VK_NULL_HANDLE) return; // Release exclusive fullscreen before destroying. if (m_current_fullscreen_state) SetFullscreenState(false); vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), m_swap_chain, nullptr); m_swap_chain = VK_NULL_HANDLE; } VkResult SwapChain::AcquireNextImage() { VkResult res = vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, g_command_buffer_mgr->GetCurrentCommandBufferSemaphore(), VK_NULL_HANDLE, &m_current_swap_chain_image_index); if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: "); return res; } bool SwapChain::ResizeSwapChain() { DestroySwapChainImages(); if (!CreateSwapChain() || !SetupSwapChainImages()) { PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); return false; } return true; } bool SwapChain::RecreateSwapChain() { DestroySwapChainImages(); DestroySwapChain(); if (!CreateSwapChain() || !SetupSwapChainImages()) { PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); return false; } return true; } bool SwapChain::SetVSync(bool enabled) { if (m_vsync_enabled == enabled) return true; // Recreate the swap chain with the new present mode. m_vsync_enabled = enabled; return RecreateSwapChain(); } bool SwapChain::SetFullscreenState(bool state) { #ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN if (m_current_fullscreen_state == state) return true; if (state) { VkResult res = vkAcquireFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkAcquireFullScreenExclusiveModeEXT failed:"); return false; } INFO_LOG_FMT(VIDEO, "Exclusive fullscreen acquired."); } else { VkResult res = vkReleaseFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain); if (res != VK_SUCCESS) LOG_VULKAN_ERROR(res, "vkReleaseFullScreenExclusiveModeEXT failed:"); INFO_LOG_FMT(VIDEO, "Exclusive fullscreen released."); } m_current_fullscreen_state = state; return true; #else return false; #endif } bool SwapChain::RecreateSurface(void* native_handle) { // Destroy the old swap chain, images, and surface. DestroySwapChainImages(); DestroySwapChain(); DestroySurface(); // Re-create the surface with the new native handle m_wsi.render_surface = native_handle; m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wsi); if (m_surface == VK_NULL_HANDLE) return false; // The validation layers get angry at us if we don't call this before creating the swapchain. VkBool32 present_supported = VK_TRUE; VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR( g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetPresentQueueFamilyIndex(), m_surface, &present_supported); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: "); return false; } if (!present_supported) { PanicAlertFmt("Recreated surface does not support presenting."); return false; } // Update exclusive fullscreen support (unlikely to change). m_fullscreen_supported = g_vulkan_context->SupportsExclusiveFullscreen(m_wsi, m_surface); g_Config.backend_info.bSupportsExclusiveFullscreen = m_fullscreen_supported; g_ActiveConfig.backend_info.bSupportsExclusiveFullscreen = m_fullscreen_supported; m_current_fullscreen_state = false; m_next_fullscreen_state = false; // Finally re-create the swap chain if (!CreateSwapChain() || !SetupSwapChainImages()) return false; return true; } void SwapChain::DestroySurface() { vkDestroySurfaceKHR(g_vulkan_context->GetVulkanInstance(), m_surface, nullptr); m_surface = VK_NULL_HANDLE; } } // namespace Vulkan