diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index b10472bed..32e3ac7ba 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -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 diff --git a/src/core/gpu_thread.cpp b/src/core/gpu_thread.cpp index 008e6bc10..09c166328 100644 --- a/src/core/gpu_thread.cpp +++ b/src/core/gpu_thread.cpp @@ -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 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 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 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(); diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index 9429d147a..1ad9d28b9 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -233,8 +233,23 @@ bool D3D11SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::Exclusiv RECT client_rc{}; GetClientRect(window_hwnd, &client_rc); + // Little bit messy... + HRESULT hr; + ComPtr 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(hr)); + return false; + } + ComPtr dxgi_adapter; + if (FAILED((hr = dxgi_dev->GetAdapter(dxgi_adapter.GetAddressOf())))) + { + ERROR_LOG("Failed to get DXGIAdapter from DXGIDevice: {:08X}", static_cast(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 D3D11Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, const ExclusiveFullscreenMode* exclusive_fullscreen_mode, diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index d445574e1..e9048047d 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -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); diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index daf394bc0..ff61a32f5 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -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; diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index 598e97716..c4599b4e5 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -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); diff --git a/src/util/d3d_common.cpp b/src/util/d3d_common.cpp index 937c52895..cc026aff8 100644 --- a/src/util/d3d_common.cpp +++ b/src/util/d3d_common.cpp @@ -225,50 +225,40 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList() } std::optional -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 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 first_output, intersecting_output; - - for (u32 adapter_index = 0; !intersecting_output; adapter_index++) + for (u32 output_index = 0;; output_index++) { - Microsoft::WRL::ComPtr adapter; - hr = factory->EnumAdapters1(adapter_index, adapter.GetAddressOf()); + Microsoft::WRL::ComPtr this_output; + DXGI_OUTPUT_DESC output_desc; + hr = adapter->EnumOutputs(output_index, this_output.GetAddressOf()); if (hr == DXGI_ERROR_NOT_FOUND) break; - else if (FAILED(hr)) + else if (FAILED(hr) || FAILED(this_output->GetDesc(&output_desc))) 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 this_output; - DXGI_OUTPUT_DESC output_desc; - 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); + intersecting_output = std::move(this_output); + break; } + + // Fallback to the first monitor. + if (!first_output) + first_output = std::move(this_output); } if (!intersecting_output) @@ -561,11 +551,7 @@ std::optional> 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); diff --git a/src/util/d3d_common.h b/src/util/d3d_common.h index 2a2f1784a..5360b847c 100644 --- a/src/util/d3d_common.h +++ b/src/util/d3d_common.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -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 -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); diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index d93342dcc..114942c35 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -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. diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 60aed7465..c7b9b2a33 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -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();