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,
fmt::format("{}{}", TRANSLATE_SV("GameDatabase", "Compatibility settings for this game have been applied."),
messages.view()),
Host::OSD_WARNING_DURATION);
Host::OSD_INFO_DURATION);
}
#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 void DestroyDeviceOnThread(bool clear_fsui_state);
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 bool CheckExclusiveFullscreenOnThread();
static void ReconfigureOnThread(GPUThreadReconfigureCommand* cmd);
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);
UpdateRunIdle();
// Switch to borderless if exclusive failed.
if (fullscreen_mode.has_value() && !CheckExclusiveFullscreenOnThread())
UpdateDisplayWindowOnThread(true, false);
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))
{
ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription());
UpdateDisplayWindowOnThread(Host::IsFullscreen());
UpdateDisplayWindowOnThread(Host::IsFullscreen(), true);
return;
}
@ -1154,20 +1160,22 @@ void GPUThread::ResizeDisplayWindowOnThread(u32 width, u32 height, float scale)
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.
if (!g_gpu_device)
return;
bool exclusive_fullscreen_requested = false;
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 =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
exclusive_fullscreen_requested = fullscreen_mode.has_value();
}
std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
@ -1180,7 +1188,7 @@ void GPUThread::UpdateDisplayWindowOnThread(bool fullscreen)
Error error;
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())
{
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());
}
// If exclusive fullscreen failed, switch to borderless fullscreen.
if (exclusive_fullscreen_requested && !CheckExclusiveFullscreenOnThread())
{
UpdateDisplayWindowOnThread(true, false);
return;
}
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()
{
const GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();

View File

@ -233,8 +233,23 @@ bool D3D11SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::Exclusiv
RECT 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(
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();
}
@ -444,6 +459,11 @@ bool D3D11SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle
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,
bool allow_present_throttle,
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* const* GetRTVArray() const { return m_swap_chain_rtv.GetAddressOf(); }
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 SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
bool IsExclusiveFullscreen() const override;
private:
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);

View File

@ -880,9 +880,8 @@ bool D3D12SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::Exclusiv
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
m_fullscreen_mode =
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(D3D12Device::GetInstance().GetDXGIFactory(), client_rc, mode,
fm.resource_format, m_fullscreen_output.GetAddressOf());
m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc(
D3D12Device::GetInstance().GetAdapter(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf());
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);
}
bool D3D12SwapChain::IsExclusiveFullscreen() const
{
return m_fullscreen_mode.has_value();
}
bool D3D12SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{
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 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 IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); }
void AdvanceBuffer()
{
@ -389,6 +388,7 @@ public:
}
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 IsExclusiveFullscreen() const override;
private:
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);

View File

@ -225,28 +225,19 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
}
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,
DXGI_FORMAT format, IDXGIOutput** output)
{
std::optional<DXGI_MODE_DESC> ret;
// 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);
// The window might be on a different adapter to which we are rendering.. so we have to enumerate them all.
HRESULT hr;
Microsoft::WRL::ComPtr<IDXGIOutput> first_output, intersecting_output;
for (u32 adapter_index = 0; !intersecting_output; adapter_index++)
{
Microsoft::WRL::ComPtr<IDXGIAdapter1> adapter;
hr = factory->EnumAdapters1(adapter_index, adapter.GetAddressOf());
if (hr == DXGI_ERROR_NOT_FOUND)
break;
else if (FAILED(hr))
continue;
for (u32 output_index = 0;; output_index++)
{
Microsoft::WRL::ComPtr<IDXGIOutput> this_output;
@ -269,7 +260,6 @@ D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const
if (!first_output)
first_output = std::move(this_output);
}
}
if (!intersecting_output)
{
@ -561,11 +551,7 @@ std::optional<DynamicHeapArray<u8>> D3DCommon::CompileShaderWithDXC(u32 shader_m
DXC_ARG_OPTIMIZATION_LEVEL3,
};
static constexpr const wchar_t* debug_arguments[] = {
L"-Qstrip_reflect",
DXC_ARG_DEBUG,
L"-Qembed_debug",
DXC_ARG_PACK_MATRIX_ROW_MAJOR,
DXC_ARG_SKIP_OPTIMIZATIONS,
L"-Qstrip_reflect", 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 size_t arguments_size = debug_device ? std::size(debug_arguments) : std::size(nondebug_arguments);

View File

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

View File

@ -275,6 +275,11 @@ GSVector4i GPUSwapChain::PreRotateClipRect(WindowInfo::PreRotation prerotation,
return new_clip;
}
bool GPUSwapChain::IsExclusiveFullscreen() const
{
return false;
}
bool GPUSwapChain::ShouldSkipPresentingFrame()
{
// 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 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();
void ThrottlePresentation();