GPUThread: Switch to borderless if exclusive fullscreen fails

Better than ending up windowed.
This commit is contained in:
Stenzek 2025-01-29 18:20:37 +10:00
parent e36dbaf255
commit 231ba050a2
No known key found for this signature in database
10 changed files with 92 additions and 46 deletions

View File

@ -716,7 +716,7 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
"GameDBCompatibility", ICON_EMOJI_INFORMATION, "GameDBCompatibility", ICON_EMOJI_INFORMATION,
fmt::format("{}{}", TRANSLATE_SV("GameDatabase", "Compatibility settings for this game have been applied."), fmt::format("{}{}", TRANSLATE_SV("GameDatabase", "Compatibility settings for this game have been applied."),
messages.view()), messages.view()),
Host::OSD_WARNING_DURATION); Host::OSD_INFO_DURATION);
} }
#undef APPEND_MESSAGE_FMT #undef APPEND_MESSAGE_FMT

View File

@ -74,8 +74,9 @@ static bool SleepGPUThread(bool allow_sleep);
static bool CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool clear_fsui_state_on_failure, Error* error); static bool CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool clear_fsui_state_on_failure, Error* error);
static void DestroyDeviceOnThread(bool clear_fsui_state); static void DestroyDeviceOnThread(bool clear_fsui_state);
static void ResizeDisplayWindowOnThread(u32 width, u32 height, float scale); static void ResizeDisplayWindowOnThread(u32 width, u32 height, float scale);
static void UpdateDisplayWindowOnThread(bool fullscreen); static void UpdateDisplayWindowOnThread(bool fullscreen, bool allow_exclusive_fullscreen);
static void DisplayWindowResizedOnThread(); static void DisplayWindowResizedOnThread();
static bool CheckExclusiveFullscreenOnThread();
static void ReconfigureOnThread(GPUThreadReconfigureCommand* cmd); static void ReconfigureOnThread(GPUThreadReconfigureCommand* cmd);
static bool CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vram, Error* error); static bool CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vram, Error* error);
@ -725,6 +726,11 @@ bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool clear_
std::atomic_thread_fence(std::memory_order_release); std::atomic_thread_fence(std::memory_order_release);
UpdateRunIdle(); UpdateRunIdle();
// Switch to borderless if exclusive failed.
if (fullscreen_mode.has_value() && !CheckExclusiveFullscreenOnThread())
UpdateDisplayWindowOnThread(true, false);
return true; return true;
} }
@ -1145,7 +1151,7 @@ void GPUThread::ResizeDisplayWindowOnThread(u32 width, u32 height, float scale)
if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error)) if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error))
{ {
ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription()); ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription());
UpdateDisplayWindowOnThread(Host::IsFullscreen()); UpdateDisplayWindowOnThread(Host::IsFullscreen(), true);
return; return;
} }
@ -1154,20 +1160,22 @@ void GPUThread::ResizeDisplayWindowOnThread(u32 width, u32 height, float scale)
void GPUThread::UpdateDisplayWindow(bool fullscreen) void GPUThread::UpdateDisplayWindow(bool fullscreen)
{ {
RunOnThread([fullscreen]() { UpdateDisplayWindowOnThread(fullscreen); }); RunOnThread([fullscreen]() { UpdateDisplayWindowOnThread(fullscreen, true); });
} }
void GPUThread::UpdateDisplayWindowOnThread(bool fullscreen) void GPUThread::UpdateDisplayWindowOnThread(bool fullscreen, bool allow_exclusive_fullscreen)
{ {
// In case we get the event late. // In case we get the event late.
if (!g_gpu_device) if (!g_gpu_device)
return; return;
bool exclusive_fullscreen_requested = false;
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode; std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
if (fullscreen && g_gpu_device->GetFeatures().exclusive_fullscreen) if (allow_exclusive_fullscreen && fullscreen && g_gpu_device->GetFeatures().exclusive_fullscreen)
{ {
fullscreen_mode = fullscreen_mode =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", "")); GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
exclusive_fullscreen_requested = fullscreen_mode.has_value();
} }
std::optional<bool> exclusive_fullscreen_control; std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic) if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
@ -1180,7 +1188,7 @@ void GPUThread::UpdateDisplayWindowOnThread(bool fullscreen)
Error error; Error error;
std::optional<WindowInfo> wi = std::optional<WindowInfo> wi =
Host::AcquireRenderWindow(g_gpu_device->GetRenderAPI(), fullscreen, fullscreen_mode.has_value(), &error); Host::AcquireRenderWindow(g_gpu_device->GetRenderAPI(), fullscreen, exclusive_fullscreen_requested, &error);
if (!wi.has_value()) if (!wi.has_value())
{ {
Host::ReportFatalError("Failed to get render window after update", error.GetDescription()); Host::ReportFatalError("Failed to get render window after update", error.GetDescription());
@ -1205,9 +1213,28 @@ void GPUThread::UpdateDisplayWindowOnThread(bool fullscreen)
ERROR_LOG("Failed to switch to surfaceless, rendering commands may fail: {}", error.GetDescription()); ERROR_LOG("Failed to switch to surfaceless, rendering commands may fail: {}", error.GetDescription());
} }
// If exclusive fullscreen failed, switch to borderless fullscreen.
if (exclusive_fullscreen_requested && !CheckExclusiveFullscreenOnThread())
{
UpdateDisplayWindowOnThread(true, false);
return;
}
DisplayWindowResizedOnThread(); DisplayWindowResizedOnThread();
} }
bool GPUThread::CheckExclusiveFullscreenOnThread()
{
if (g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->IsExclusiveFullscreen())
return true;
Host::AddIconOSDWarning(
"ExclusiveFullscreenFailed", ICON_EMOJI_WARNING,
TRANSLATE_STR("OSDMessage", "Failed to switch to exclusive fullscreen, using borderless instead."),
Host::OSD_INFO_DURATION);
return false;
}
void GPUThread::DisplayWindowResizedOnThread() void GPUThread::DisplayWindowResizedOnThread()
{ {
const GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain(); const GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();

View File

@ -233,8 +233,23 @@ bool D3D11SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::Exclusiv
RECT client_rc{}; RECT client_rc{};
GetClientRect(window_hwnd, &client_rc); GetClientRect(window_hwnd, &client_rc);
// Little bit messy...
HRESULT hr;
ComPtr<IDXGIDevice> dxgi_dev;
if (FAILED((hr = D3D11Device::GetD3DDevice()->QueryInterface(IID_PPV_ARGS(dxgi_dev.GetAddressOf())))))
{
ERROR_LOG("Failed to get DXGIDevice from D3D device: {:08X}", static_cast<unsigned>(hr));
return false;
}
ComPtr<IDXGIAdapter> dxgi_adapter;
if (FAILED((hr = dxgi_dev->GetAdapter(dxgi_adapter.GetAddressOf()))))
{
ERROR_LOG("Failed to get DXGIAdapter from DXGIDevice: {:08X}", static_cast<unsigned>(hr));
return false;
}
m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc( m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc(
D3D11Device::GetDXGIFactory(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf()); dxgi_adapter.Get(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf());
return m_fullscreen_mode.has_value(); return m_fullscreen_mode.has_value();
} }
@ -444,6 +459,11 @@ bool D3D11SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle
return CreateSwapChain(error) && CreateRTV(error); return CreateSwapChain(error) && CreateRTV(error);
} }
bool D3D11SwapChain::IsExclusiveFullscreen() const
{
return m_fullscreen_mode.has_value();
}
std::unique_ptr<GPUSwapChain> D3D11Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, std::unique_ptr<GPUSwapChain> D3D11Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode, const ExclusiveFullscreenMode* exclusive_fullscreen_mode,

View File

@ -224,10 +224,10 @@ public:
ALWAYS_INLINE ID3D11RenderTargetView* GetRTV() const { return m_swap_chain_rtv.Get(); } ALWAYS_INLINE ID3D11RenderTargetView* GetRTV() const { return m_swap_chain_rtv.Get(); }
ALWAYS_INLINE ID3D11RenderTargetView* const* GetRTVArray() const { return m_swap_chain_rtv.GetAddressOf(); } ALWAYS_INLINE ID3D11RenderTargetView* const* GetRTVArray() const { return m_swap_chain_rtv.GetAddressOf(); }
ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; } ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; }
ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); }
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override; bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override; bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
bool IsExclusiveFullscreen() const override;
private: private:
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode); static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);

View File

@ -880,9 +880,8 @@ bool D3D12SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::Exclusiv
RECT client_rc{}; RECT client_rc{};
GetClientRect(window_hwnd, &client_rc); GetClientRect(window_hwnd, &client_rc);
m_fullscreen_mode = m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc(
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(D3D12Device::GetInstance().GetDXGIFactory(), client_rc, mode, D3D12Device::GetInstance().GetAdapter(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf());
fm.resource_format, m_fullscreen_output.GetAddressOf());
return m_fullscreen_mode.has_value(); return m_fullscreen_mode.has_value();
} }
@ -1082,6 +1081,11 @@ bool D3D12SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle
return CreateSwapChain(dev, error) && CreateRTV(dev, error); return CreateSwapChain(dev, error) && CreateRTV(dev, error);
} }
bool D3D12SwapChain::IsExclusiveFullscreen() const
{
return m_fullscreen_mode.has_value();
}
bool D3D12SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) bool D3D12SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{ {
m_window_info.surface_scale = new_scale; m_window_info.surface_scale = new_scale;

View File

@ -381,7 +381,6 @@ public:
ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); } ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); }
ALWAYS_INLINE const BufferPair& GetCurrentBuffer() const { return m_swap_chain_buffers[m_current_swap_chain_buffer]; } ALWAYS_INLINE const BufferPair& GetCurrentBuffer() const { return m_swap_chain_buffers[m_current_swap_chain_buffer]; }
ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; } ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; }
ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); }
void AdvanceBuffer() void AdvanceBuffer()
{ {
@ -389,6 +388,7 @@ public:
} }
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override; bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override; bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
bool IsExclusiveFullscreen() const override;
private: private:
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode); static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);

View File

@ -225,50 +225,40 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
} }
std::optional<DXGI_MODE_DESC> std::optional<DXGI_MODE_DESC>
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIAdapter* adapter, const RECT& window_rect,
const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode, const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode,
DXGI_FORMAT format, IDXGIOutput** output) DXGI_FORMAT format, IDXGIOutput** output)
{ {
std::optional<DXGI_MODE_DESC> ret; std::optional<DXGI_MODE_DESC> ret;
// We need to find which monitor the window is located on. // We need to find which monitor the window is located on.
// The adapter must match, you cannot restrict the output to a monitor that is not connected to the device.
const GSVector4i client_rc_vec(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom); const GSVector4i client_rc_vec(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom);
// The window might be on a different adapter to which we are rendering.. so we have to enumerate them all. // The window might be on a different adapter to which we are rendering.. so we have to enumerate them all.
HRESULT hr; HRESULT hr;
Microsoft::WRL::ComPtr<IDXGIOutput> first_output, intersecting_output; Microsoft::WRL::ComPtr<IDXGIOutput> first_output, intersecting_output;
for (u32 output_index = 0;; output_index++)
for (u32 adapter_index = 0; !intersecting_output; adapter_index++)
{ {
Microsoft::WRL::ComPtr<IDXGIAdapter1> adapter; Microsoft::WRL::ComPtr<IDXGIOutput> this_output;
hr = factory->EnumAdapters1(adapter_index, adapter.GetAddressOf()); DXGI_OUTPUT_DESC output_desc;
hr = adapter->EnumOutputs(output_index, this_output.GetAddressOf());
if (hr == DXGI_ERROR_NOT_FOUND) if (hr == DXGI_ERROR_NOT_FOUND)
break; break;
else if (FAILED(hr)) else if (FAILED(hr) || FAILED(this_output->GetDesc(&output_desc)))
continue; continue;
for (u32 output_index = 0;; output_index++) const GSVector4i output_rc(output_desc.DesktopCoordinates.left, output_desc.DesktopCoordinates.top,
output_desc.DesktopCoordinates.right, output_desc.DesktopCoordinates.bottom);
if (!client_rc_vec.rintersects(output_rc))
{ {
Microsoft::WRL::ComPtr<IDXGIOutput> this_output; intersecting_output = std::move(this_output);
DXGI_OUTPUT_DESC output_desc; break;
hr = adapter->EnumOutputs(output_index, this_output.GetAddressOf());
if (hr == DXGI_ERROR_NOT_FOUND)
break;
else if (FAILED(hr) || FAILED(this_output->GetDesc(&output_desc)))
continue;
const GSVector4i output_rc(output_desc.DesktopCoordinates.left, output_desc.DesktopCoordinates.top,
output_desc.DesktopCoordinates.right, output_desc.DesktopCoordinates.bottom);
if (!client_rc_vec.rintersects(output_rc))
{
intersecting_output = std::move(this_output);
break;
}
// Fallback to the first monitor.
if (!first_output)
first_output = std::move(this_output);
} }
// Fallback to the first monitor.
if (!first_output)
first_output = std::move(this_output);
} }
if (!intersecting_output) if (!intersecting_output)
@ -561,11 +551,7 @@ std::optional<DynamicHeapArray<u8>> D3DCommon::CompileShaderWithDXC(u32 shader_m
DXC_ARG_OPTIMIZATION_LEVEL3, DXC_ARG_OPTIMIZATION_LEVEL3,
}; };
static constexpr const wchar_t* debug_arguments[] = { static constexpr const wchar_t* debug_arguments[] = {
L"-Qstrip_reflect", L"-Qstrip_reflect", DXC_ARG_DEBUG, L"-Qembed_debug", DXC_ARG_PACK_MATRIX_ROW_MAJOR, DXC_ARG_SKIP_OPTIMIZATIONS,
DXC_ARG_DEBUG,
L"-Qembed_debug",
DXC_ARG_PACK_MATRIX_ROW_MAJOR,
DXC_ARG_SKIP_OPTIMIZATIONS,
}; };
const wchar_t* const* arguments = debug_device ? debug_arguments : nondebug_arguments; const wchar_t* const* arguments = debug_device ? debug_arguments : nondebug_arguments;
const size_t arguments_size = debug_device ? std::size(debug_arguments) : std::size(nondebug_arguments); const size_t arguments_size = debug_device ? std::size(debug_arguments) : std::size(nondebug_arguments);

View File

@ -11,6 +11,7 @@
#include <d3dcommon.h> #include <d3dcommon.h>
#include <dxgiformat.h> #include <dxgiformat.h>
#include <dxgitype.h>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -19,9 +20,9 @@
class Error; class Error;
struct IDXGIFactory5; struct IDXGIFactory5;
struct IDXGIAdapter;
struct IDXGIAdapter1; struct IDXGIAdapter1;
struct IDXGIOutput; struct IDXGIOutput;
struct DXGI_MODE_DESC;
namespace D3DCommon { namespace D3DCommon {
// returns string representation of feature level // returns string representation of feature level
@ -42,7 +43,7 @@ GPUDevice::AdapterInfoList GetAdapterInfoList();
// returns the fullscreen mode to use for the specified dimensions // returns the fullscreen mode to use for the specified dimensions
std::optional<DXGI_MODE_DESC> std::optional<DXGI_MODE_DESC>
GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, GetRequestedExclusiveFullscreenModeDesc(IDXGIAdapter* adapter, const RECT& window_rect,
const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode, const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode,
DXGI_FORMAT format, IDXGIOutput** output); DXGI_FORMAT format, IDXGIOutput** output);

View File

@ -275,6 +275,11 @@ GSVector4i GPUSwapChain::PreRotateClipRect(WindowInfo::PreRotation prerotation,
return new_clip; return new_clip;
} }
bool GPUSwapChain::IsExclusiveFullscreen() const
{
return false;
}
bool GPUSwapChain::ShouldSkipPresentingFrame() bool GPUSwapChain::ShouldSkipPresentingFrame()
{ {
// Only needed with FIFO. But since we're so fast, we allow it always. // Only needed with FIFO. But since we're so fast, we allow it always.

View File

@ -528,6 +528,9 @@ public:
virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0; virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0;
virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0; virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0;
/// Returns true if exclusive fullscreen is currently active on this swap chain.
virtual bool IsExclusiveFullscreen() const;
bool ShouldSkipPresentingFrame(); bool ShouldSkipPresentingFrame();
void ThrottlePresentation(); void ThrottlePresentation();