Skip to content

Commit

Permalink
Merge pull request #8372 from stenzek/vulkan-exclusive-fullscreen
Browse files Browse the repository at this point in the history
Vulkan: Exclusive fullscreen support via VK_EXT_full_screen_exclusive
  • Loading branch information
stenzek authored Oct 31, 2019
2 parents d3ee0a4 + 16f103a commit c6841a0
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 55 deletions.
13 changes: 8 additions & 5 deletions Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand Down
6 changes: 4 additions & 2 deletions Source/Core/VideoBackends/Vulkan/CommandBufferManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ class CommandBufferManager
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.
Expand Down Expand Up @@ -136,7 +137,8 @@ class CommandBufferManager
VkSemaphore m_present_semaphore = VK_NULL_HANDLE;
std::deque<PendingCommandBufferSubmit> 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;
};

Expand Down
47 changes: 43 additions & 4 deletions Source/Core/VideoBackends/Vulkan/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/VideoBackends/Vulkan/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class Renderer : public ::Renderer
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.
Expand Down
80 changes: 77 additions & 3 deletions Source/Core/VideoBackends/Vulkan/SwapChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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: ");
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 15 additions & 1 deletion Source/Core/VideoBackends/Vulkan/SwapChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ class SwapChain
// 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();
Expand All @@ -86,7 +97,10 @@ class SwapChain
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<SwapChainImage> m_swap_chain_images;
Expand Down
Loading

0 comments on commit c6841a0

Please sign in to comment.