diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp index 22f97b8888..59382166a9 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp @@ -371,13 +371,16 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index, &present_image_index, nullptr}; - res = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info); - if (res != VK_SUCCESS) + m_last_present_result = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info); + if (m_last_present_result != VK_SUCCESS) { // VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain. - if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) - LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: "); - m_present_failed_flag.Set(); + if (m_last_present_result != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR && + m_last_present_result != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) + { + LOG_VULKAN_ERROR(m_last_present_result, "vkQueuePresentKHR failed: "); + } + m_last_present_failed.Set(); } } diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h index 350a576c04..da93553d70 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h @@ -79,7 +79,8 @@ public: uint32_t present_image_index = 0xFFFFFFFF); // Was the last present submitted to the queue a failure? If so, we must recreate our swapchain. - bool CheckLastPresentFail() { return m_present_failed_flag.TestAndClear(); } + bool CheckLastPresentFail() { return m_last_present_failed.TestAndClear(); } + VkResult GetLastPresentResult() const { return m_last_present_result; } // Schedule a vulkan resource for destruction later on. This will occur when the command buffer // is next re-used, and the GPU has finished working with the specified resource. @@ -136,7 +137,8 @@ private: VkSemaphore m_present_semaphore = VK_NULL_HANDLE; std::deque m_pending_submits; std::mutex m_pending_submit_lock; - Common::Flag m_present_failed_flag; + Common::Flag m_last_present_failed; + VkResult m_last_present_result = VK_SUCCESS; bool m_use_threaded_submission = false; }; diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 648b019971..0628a5482b 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -280,13 +280,39 @@ void Renderer::BindBackbuffer(const ClearColor& clear_color) CheckForSurfaceChange(); CheckForSurfaceResize(); - VkResult res = g_command_buffer_mgr->CheckLastPresentFail() ? VK_ERROR_OUT_OF_DATE_KHR : - m_swap_chain->AcquireNextImage(); - if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) + // Check for exclusive fullscreen request. + if (m_swap_chain->GetCurrentFullscreenState() != m_swap_chain->GetNextFullscreenState() && + !m_swap_chain->SetFullscreenState(m_swap_chain->GetNextFullscreenState())) + { + // if it fails, don't keep trying + m_swap_chain->SetNextFullscreenState(m_swap_chain->GetCurrentFullscreenState()); + } + + VkResult res = g_command_buffer_mgr->CheckLastPresentFail() ? + g_command_buffer_mgr->GetLastPresentResult() : + m_swap_chain->AcquireNextImage(); + if (res != VK_SUCCESS) { // Execute cmdbuffer before resizing, as the last frame could still be presenting. ExecuteCommandBuffer(false, true); - m_swap_chain->ResizeSwapChain(); + + // Was this a lost exclusive fullscreen? + if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) + { + // The present keeps returning exclusive mode lost unless we re-create the swap chain. + INFO_LOG(VIDEO, "Lost exclusive fullscreen."); + m_swap_chain->RecreateSwapChain(); + } + else if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) + { + m_swap_chain->ResizeSwapChain(); + } + else + { + ERROR_LOG(VIDEO, "Unknown present error 0x%08X, please report.", res); + m_swap_chain->RecreateSwapChain(); + } + res = m_swap_chain->AcquireNextImage(); } if (res != VK_SUCCESS) @@ -323,6 +349,19 @@ void Renderer::PresentBackbuffer() StateTracker::GetInstance()->InvalidateCachedState(); } +void Renderer::SetFullscreen(bool enable_fullscreen) +{ + if (!m_swap_chain->IsFullscreenSupported()) + return; + + m_swap_chain->SetNextFullscreenState(enable_fullscreen); +} + +bool Renderer::IsFullscreen() const +{ + return m_swap_chain && m_swap_chain->GetCurrentFullscreenState(); +} + void Renderer::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion) { StateTracker::GetInstance()->EndRenderPass(); diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h index 245ad777ec..43ed3bd45b 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.h +++ b/Source/Core/VideoBackends/Vulkan/Renderer.h @@ -83,6 +83,8 @@ public: u32 groups_z) override; void BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; + void SetFullscreen(bool enable_fullscreen) override; + bool IsFullscreen() const override; // Completes the current render pass, executes the command buffer, and restores state ready for // next render. Use when you want to kick the current buffer to make room for new data. diff --git a/Source/Core/VideoBackends/Vulkan/SwapChain.cpp b/Source/Core/VideoBackends/Vulkan/SwapChain.cpp index ef9b6f7265..66f20e5a6e 100644 --- a/Source/Core/VideoBackends/Vulkan/SwapChain.cpp +++ b/Source/Core/VideoBackends/Vulkan/SwapChain.cpp @@ -25,7 +25,8 @@ namespace Vulkan { SwapChain::SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync) - : m_wsi(wsi), m_surface(surface), m_vsync_enabled(vsync) + : m_wsi(wsi), m_surface(surface), m_vsync_enabled(vsync), + m_fullscreen_supported(g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface)) { } @@ -290,6 +291,7 @@ bool SwapChain::CreateSwapChain() // 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, @@ -322,8 +324,36 @@ bool SwapChain::CreateSwapChain() swap_chain_info.pQueueFamilyIndices = indices.data(); } - res = - vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, &m_swap_chain); +#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(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: "); @@ -414,6 +444,10 @@ 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; } @@ -464,6 +498,39 @@ bool SwapChain::SetVSync(bool 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(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(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. @@ -493,6 +560,13 @@ bool SwapChain::RecreateSurface(void* native_handle) 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; diff --git a/Source/Core/VideoBackends/Vulkan/SwapChain.h b/Source/Core/VideoBackends/Vulkan/SwapChain.h index 4359f4663c..a6ee28d965 100644 --- a/Source/Core/VideoBackends/Vulkan/SwapChain.h +++ b/Source/Core/VideoBackends/Vulkan/SwapChain.h @@ -62,6 +62,17 @@ public: // Change vsync enabled state. This may fail as it causes a swapchain recreation. bool SetVSync(bool enabled); + // Is exclusive fullscreen supported? + bool IsFullscreenSupported() const { return m_fullscreen_supported; } + + // Retrieves the "next" fullscreen state. Safe to call off-thread. + bool GetCurrentFullscreenState() const { return m_current_fullscreen_state; } + bool GetNextFullscreenState() const { return m_next_fullscreen_state; } + void SetNextFullscreenState(bool state) { m_next_fullscreen_state = state; } + + // Updates the fullscreen state. Must call on-thread. + bool SetFullscreenState(bool state); + private: bool SelectSurfaceFormat(); bool SelectPresentMode(); @@ -86,7 +97,10 @@ private: VkSurfaceFormatKHR m_surface_format = {}; VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_RANGE_SIZE_KHR; AbstractTextureFormat m_texture_format = AbstractTextureFormat::Undefined; - bool m_vsync_enabled; + bool m_vsync_enabled = false; + bool m_fullscreen_supported = false; + bool m_current_fullscreen_state = false; + bool m_next_fullscreen_state = false; VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE; std::vector m_swap_chain_images; diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp index 72eddd56c9..dc24839f88 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp @@ -88,7 +88,7 @@ bool VulkanContext::CheckValidationLayerAvailablility() VkInstance VulkanContext::CreateVulkanInstance(WindowSystemType wstype, bool enable_debug_report, bool enable_validation_layer) { - ExtensionList enabled_extensions; + std::vector enabled_extensions; if (!SelectInstanceExtensions(&enabled_extensions, wstype, enable_debug_report)) return VK_NULL_HANDLE; @@ -143,8 +143,8 @@ VkInstance VulkanContext::CreateVulkanInstance(WindowSystemType wstype, bool ena return instance; } -bool VulkanContext::SelectInstanceExtensions(ExtensionList* extension_list, WindowSystemType wstype, - bool enable_debug_report) +bool VulkanContext::SelectInstanceExtensions(std::vector* extension_list, + WindowSystemType wstype, bool enable_debug_report) { u32 extension_count = 0; VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); @@ -168,7 +168,7 @@ bool VulkanContext::SelectInstanceExtensions(ExtensionList* extension_list, Wind for (const auto& extension_properties : available_extension_list) INFO_LOG(VIDEO, "Available extension: %s", extension_properties.extensionName); - auto SupportsExtension = [&](const char* name, bool required) { + auto AddExtension = [&](const char* name, bool required) { if (std::find_if(available_extension_list.begin(), available_extension_list.end(), [&](const VkExtensionProperties& properties) { return !strcmp(name, properties.extensionName); @@ -186,45 +186,45 @@ bool VulkanContext::SelectInstanceExtensions(ExtensionList* extension_list, Wind }; // Common extensions - if (wstype != WindowSystemType::Headless && - !SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true)) + if (wstype != WindowSystemType::Headless && !AddExtension(VK_KHR_SURFACE_EXTENSION_NAME, true)) { return false; } #if defined(VK_USE_PLATFORM_WIN32_KHR) if (wstype == WindowSystemType::Windows && - !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true)) + !AddExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true)) { return false; } #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) - if (wstype == WindowSystemType::X11 && - !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true)) + if (wstype == WindowSystemType::X11 && !AddExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true)) { return false; } #endif #if defined(VK_USE_PLATFORM_ANDROID_KHR) if (wstype == WindowSystemType::Android && - !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true)) + !AddExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true)) { return false; } #endif #if defined(VK_USE_PLATFORM_MACOS_MVK) - if (wstype == WindowSystemType::MacOS && - !SupportsExtension(VK_MVK_MACOS_SURFACE_EXTENSION_NAME, true)) + if (wstype == WindowSystemType::MacOS && !AddExtension(VK_MVK_MACOS_SURFACE_EXTENSION_NAME, true)) { return false; } #endif // VK_EXT_debug_report - if (enable_debug_report && !SupportsExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, false)) + if (enable_debug_report && !AddExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, false)) WARN_LOG(VIDEO, "Vulkan: Debug report requested, but extension is not available."); + AddExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false); + AddExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false); + return true; } @@ -254,26 +254,26 @@ VulkanContext::GPUList VulkanContext::EnumerateGPUs(VkInstance instance) void VulkanContext::PopulateBackendInfo(VideoConfig* config) { config->backend_info.api_type = APIType::Vulkan; - config->backend_info.bSupportsExclusiveFullscreen = false; // Currently WSI does not allow this. - config->backend_info.bSupports3DVision = false; // D3D-exclusive. - config->backend_info.bSupportsOversizedViewports = true; // Assumed support. - config->backend_info.bSupportsEarlyZ = true; // Assumed support. - config->backend_info.bSupportsPrimitiveRestart = true; // Assumed support. - config->backend_info.bSupportsBindingLayout = false; // Assumed support. - config->backend_info.bSupportsPaletteConversion = true; // Assumed support. - config->backend_info.bSupportsClipControl = true; // Assumed support. - config->backend_info.bSupportsMultithreading = true; // Assumed support. - config->backend_info.bSupportsComputeShaders = true; // Assumed support. - config->backend_info.bSupportsGPUTextureDecoding = true; // Assumed support. - config->backend_info.bSupportsBitfield = true; // Assumed support. - config->backend_info.bSupportsPartialDepthCopies = true; // Assumed support. - config->backend_info.bSupportsShaderBinaries = true; // Assumed support. - config->backend_info.bSupportsPipelineCacheData = false; // Handled via pipeline caches. + config->backend_info.bSupports3DVision = false; // D3D-exclusive. + config->backend_info.bSupportsOversizedViewports = true; // Assumed support. + config->backend_info.bSupportsEarlyZ = true; // Assumed support. + config->backend_info.bSupportsPrimitiveRestart = true; // Assumed support. + config->backend_info.bSupportsBindingLayout = false; // Assumed support. + config->backend_info.bSupportsPaletteConversion = true; // Assumed support. + config->backend_info.bSupportsClipControl = true; // Assumed support. + config->backend_info.bSupportsMultithreading = true; // Assumed support. + config->backend_info.bSupportsComputeShaders = true; // Assumed support. + config->backend_info.bSupportsGPUTextureDecoding = true; // Assumed support. + config->backend_info.bSupportsBitfield = true; // Assumed support. + config->backend_info.bSupportsPartialDepthCopies = true; // Assumed support. + config->backend_info.bSupportsShaderBinaries = true; // Assumed support. + config->backend_info.bSupportsPipelineCacheData = false; // Handled via pipeline caches. config->backend_info.bSupportsDynamicSamplerIndexing = true; // Assumed support. config->backend_info.bSupportsPostProcessing = true; // Assumed support. config->backend_info.bSupportsBackgroundCompiling = true; // Assumed support. config->backend_info.bSupportsCopyToVram = true; // Assumed support. config->backend_info.bSupportsReversedDepthRange = true; // Assumed support. + config->backend_info.bSupportsExclusiveFullscreen = false; // Dependent on OS and features. config->backend_info.bSupportsDualSourceBlend = false; // Dependent on features. config->backend_info.bSupportsGeometryShaders = false; // Dependent on features. config->backend_info.bSupportsGSInstancing = false; // Dependent on features. @@ -424,7 +424,7 @@ std::unique_ptr VulkanContext::Create(VkInstance instance, VkPhys return context; } -bool VulkanContext::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface) +bool VulkanContext::SelectDeviceExtensions(bool enable_surface) { u32 extension_count = 0; VkResult res = @@ -449,14 +449,14 @@ bool VulkanContext::SelectDeviceExtensions(ExtensionList* extension_list, bool e for (const auto& extension_properties : available_extension_list) INFO_LOG(VIDEO, "Available extension: %s", extension_properties.extensionName); - auto SupportsExtension = [&](const char* name, bool required) { + auto AddExtension = [&](const char* name, bool required) { if (std::find_if(available_extension_list.begin(), available_extension_list.end(), [&](const VkExtensionProperties& properties) { return !strcmp(name, properties.extensionName); }) != available_extension_list.end()) { INFO_LOG(VIDEO, "Enabling extension: %s", name); - extension_list->push_back(name); + m_device_extensions.push_back(name); return true; } @@ -466,9 +466,15 @@ bool VulkanContext::SelectDeviceExtensions(ExtensionList* extension_list, bool e return false; }; - if (enable_surface && !SupportsExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true)) + if (enable_surface && !AddExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true)) return false; +#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN + // VK_EXT_full_screen_exclusive + if (AddExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, true)) + INFO_LOG(VIDEO, "Using VK_EXT_full_screen_exclusive for exclusive fullscreen."); +#endif + return true; } @@ -606,14 +612,18 @@ bool VulkanContext::CreateDevice(VkSurfaceKHR surface, bool enable_validation_la } device_info.pQueueCreateInfos = queue_infos.data(); - ExtensionList enabled_extensions; - if (!SelectDeviceExtensions(&enabled_extensions, surface != VK_NULL_HANDLE)) + if (!SelectDeviceExtensions(surface != VK_NULL_HANDLE)) return false; + // convert std::string list to a char pointer list which we can feed in + std::vector extension_name_pointers; + for (const std::string& name : m_device_extensions) + extension_name_pointers.push_back(name.c_str()); + device_info.enabledLayerCount = 0; device_info.ppEnabledLayerNames = nullptr; - device_info.enabledExtensionCount = static_cast(enabled_extensions.size()); - device_info.ppEnabledExtensionNames = enabled_extensions.data(); + device_info.enabledExtensionCount = static_cast(extension_name_pointers.size()); + device_info.ppEnabledExtensionNames = extension_name_pointers.data(); // Check for required features before creating. if (!SelectDeviceFeatures()) @@ -813,6 +823,12 @@ u32 VulkanContext::GetReadbackMemoryType(u32 bits, bool* is_coherent) return 0; } +bool VulkanContext::SupportsDeviceExtension(const char* name) const +{ + return std::any_of(m_device_extensions.begin(), m_device_extensions.end(), + [name](const std::string& extension) { return extension == name; }); +} + void VulkanContext::InitDriverDetails() { DriverDetails::Vendor vendor; @@ -928,4 +944,54 @@ void VulkanContext::PopulateShaderSubgroupSupport() (subgroup_properties.supportedOperations & required_operations) == required_operations && subgroup_properties.supportedStages & VK_SHADER_STAGE_FRAGMENT_BIT; } + +bool VulkanContext::SupportsExclusiveFullscreen(const WindowSystemInfo& wsi, VkSurfaceKHR surface) +{ +#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN + if (!surface || !vkGetPhysicalDeviceSurfaceCapabilities2KHR || + !SupportsDeviceExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME)) + { + return false; + } + + VkPhysicalDeviceSurfaceInfo2KHR si = {}; + si.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR; + si.surface = surface; + + auto platform_info = GetPlatformExclusiveFullscreenInfo(wsi); + si.pNext = &platform_info; + + VkSurfaceCapabilities2KHR caps = {}; + caps.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR; + + VkSurfaceCapabilitiesFullScreenExclusiveEXT fullscreen_caps = {}; + fullscreen_caps.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT; + fullscreen_caps.fullScreenExclusiveSupported = VK_TRUE; + caps.pNext = &fullscreen_caps; + + VkResult res = vkGetPhysicalDeviceSurfaceCapabilities2KHR(m_physical_device, &si, &caps); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilities2KHR failed:"); + return false; + } + + return fullscreen_caps.fullScreenExclusiveSupported; +#else + return false; +#endif +} + +#ifdef WIN32 +VkSurfaceFullScreenExclusiveWin32InfoEXT +VulkanContext::GetPlatformExclusiveFullscreenInfo(const WindowSystemInfo& wsi) +{ + VkSurfaceFullScreenExclusiveWin32InfoEXT info = {}; + info.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT; + info.hmonitor = + MonitorFromWindow(static_cast(wsi.render_surface), MONITOR_DEFAULTTOPRIMARY); + return info; +} +#endif + } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.h b/Source/Core/VideoBackends/Vulkan/VulkanContext.h index e33e5b3716..8e22daf603 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.h +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "Common/CommonTypes.h" @@ -107,11 +108,22 @@ public: u32 GetUploadMemoryType(u32 bits, bool* is_coherent = nullptr); u32 GetReadbackMemoryType(u32 bits, bool* is_coherent = nullptr); + // Returns true if the specified extension is supported and enabled. + bool SupportsDeviceExtension(const char* name) const; + + // Returns true if exclusive fullscreen is supported for the given surface. + bool SupportsExclusiveFullscreen(const WindowSystemInfo& wsi, VkSurfaceKHR surface); + +#ifdef WIN32 + // Returns the platform-specific exclusive fullscreen structure. + VkSurfaceFullScreenExclusiveWin32InfoEXT + GetPlatformExclusiveFullscreenInfo(const WindowSystemInfo& wsi); +#endif + private: - using ExtensionList = std::vector; - static bool SelectInstanceExtensions(ExtensionList* extension_list, WindowSystemType wstype, - bool enable_debug_report); - bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface); + static bool SelectInstanceExtensions(std::vector* extension_list, + WindowSystemType wstype, bool enable_debug_report); + bool SelectDeviceExtensions(bool enable_surface); bool SelectDeviceFeatures(); bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer); void InitDriverDetails(); @@ -135,6 +147,8 @@ private: u32 m_shader_subgroup_size = 1; bool m_supports_shader_subgroup_operations = false; + + std::vector m_device_extensions; }; extern std::unique_ptr g_vulkan_context; diff --git a/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl b/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl index 874cdf9a48..97cbff969e 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl +++ b/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl @@ -61,6 +61,7 @@ VULKAN_INSTANCE_ENTRY_POINT(vkCreateDebugReportCallbackEXT, false) VULKAN_INSTANCE_ENTRY_POINT(vkDestroyDebugReportCallbackEXT, false) VULKAN_INSTANCE_ENTRY_POINT(vkDebugReportMessageEXT, false) VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceProperties2, false) +VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceSurfaceCapabilities2KHR, false) #endif // VULKAN_INSTANCE_ENTRY_POINT @@ -192,4 +193,9 @@ VULKAN_DEVICE_ENTRY_POINT(vkGetSwapchainImagesKHR, false) VULKAN_DEVICE_ENTRY_POINT(vkAcquireNextImageKHR, false) VULKAN_DEVICE_ENTRY_POINT(vkQueuePresentKHR, false) +#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN +VULKAN_DEVICE_ENTRY_POINT(vkAcquireFullScreenExclusiveModeEXT, false) +VULKAN_DEVICE_ENTRY_POINT(vkReleaseFullScreenExclusiveModeEXT, false) +#endif + #endif // VULKAN_DEVICE_ENTRY_POINT diff --git a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h index 1bb0cae867..76fe45e037 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h +++ b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h @@ -24,6 +24,11 @@ #include "vulkan/vulkan.h" +// Currently, exclusive fullscreen is only supported on Windows. +#if defined(WIN32) +#define SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN 1 +#endif + // We abuse the preprocessor here to only need to specify function names once. #define VULKAN_MODULE_ENTRY_POINT(name, required) extern PFN_##name name; #define VULKAN_INSTANCE_ENTRY_POINT(name, required) extern PFN_##name name; diff --git a/Source/Core/VideoBackends/Vulkan/main.cpp b/Source/Core/VideoBackends/Vulkan/main.cpp index bc7ae8c067..425bcbae79 100644 --- a/Source/Core/VideoBackends/Vulkan/main.cpp +++ b/Source/Core/VideoBackends/Vulkan/main.cpp @@ -186,6 +186,8 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) g_vulkan_context->GetDeviceFeatures()); VulkanContext::PopulateBackendInfoMultisampleModes( &g_Config, g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetDeviceProperties()); + g_Config.backend_info.bSupportsExclusiveFullscreen = + enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface); // With the backend information populated, we can now initialize videocommon. InitializeShared();