diff --git a/src/core/system.cpp b/src/core/system.cpp index 9bfcf9ba0..029a20fa3 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2845,12 +2845,11 @@ void System::UpdateSpeedLimiterState() if (g_settings.sync_to_host_refresh_rate && (g_settings.audio_stream_parameters.stretch_mode != AudioStretchMode::Off) && s_target_speed == 1.0f && IsValid()) { - float host_refresh_rate; - if (g_gpu_device->GetHostRefreshRate(&host_refresh_rate)) + if (std::optional host_refresh_rate = g_gpu_device->GetHostRefreshRate(); host_refresh_rate.has_value()) { - const float ratio = host_refresh_rate / System::GetThrottleFrequency(); + const float ratio = host_refresh_rate.value() / System::GetThrottleFrequency(); s_syncing_to_host = (ratio >= 0.95f && ratio <= 1.05f); - Log_InfoPrintf("Refresh rate: Host=%fhz Guest=%fhz Ratio=%f - %s", host_refresh_rate, + Log_InfoPrintf("Refresh rate: Host=%fhz Guest=%fhz Ratio=%f - %s", host_refresh_rate.value(), System::GetThrottleFrequency(), ratio, s_syncing_to_host ? "can sync" : "can't sync"); if (s_syncing_to_host) s_target_speed *= ratio; diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index b860bf629..6d16ddc67 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -585,7 +585,7 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t) static_cast(t)->CommitClear(m_context.Get()); } -bool D3D11Device::GetHostRefreshRate(float* refresh_rate) +std::optional D3D11Device::GetHostRefreshRate() { if (m_swap_chain && m_is_exclusive_fullscreen) { @@ -595,13 +595,12 @@ bool D3D11Device::GetHostRefreshRate(float* refresh_rate) { Log_InfoPrintf("using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator); - *refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / - static_cast(desc.BufferDesc.RefreshRate.Denominator); - return true; + return static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); } } - return GPUDevice::GetHostRefreshRate(refresh_rate); + return GPUDevice::GetHostRefreshRate(); } bool D3D11Device::BeginPresent(bool skip_present) diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index e33884eb3..e8099b8a3 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -96,7 +96,7 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - bool GetHostRefreshRate(float* refresh_rate) override; + std::optional GetHostRefreshRate() override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 11b1902b8..46eba60cf 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -1052,15 +1052,12 @@ void GPUDevice::ThrottlePresentation() Common::Timer::SleepUntil(m_last_frame_displayed_time, false); } -bool GPUDevice::GetHostRefreshRate(float* refresh_rate) +std::optional GPUDevice::GetHostRefreshRate() { if (m_window_info.surface_refresh_rate > 0.0f) - { - *refresh_rate = m_window_info.surface_refresh_rate; - return true; - } + return m_window_info.surface_refresh_rate; - return WindowInfo::QueryRefreshRateForWindow(m_window_info, refresh_rate); + return WindowInfo::QueryRefreshRateForWindow(m_window_info); } bool GPUDevice::SetGPUTimingEnabled(bool enabled) diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 37331a209..021c5af15 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -689,7 +689,7 @@ public: virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0; - virtual bool GetHostRefreshRate(float* refresh_rate); + virtual std::optional GetHostRefreshRate(); /// Enables/disables GPU frame timing. virtual bool SetGPUTimingEnabled(bool enabled); diff --git a/src/util/metal_device.h b/src/util/metal_device.h index df2ed7388..6e8bcc7bd 100644 --- a/src/util/metal_device.h +++ b/src/util/metal_device.h @@ -260,7 +260,7 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - bool GetHostRefreshRate(float* refresh_rate) override; + std::optional GetHostRefreshRate() override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 2cebe65ee..4635984e8 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -119,9 +119,9 @@ bool MetalDevice::HasSurface() const return (m_layer != nil); } -bool MetalDevice::GetHostRefreshRate(float* refresh_rate) +std::optional MetalDevice::GetHostRefreshRate() { - return GPUDevice::GetHostRefreshRate(refresh_rate); + return GPUDevice::GetHostRefreshRate(); } void MetalDevice::SetVSyncEnabled(bool enabled) diff --git a/src/util/window_info.cpp b/src/util/window_info.cpp index a12db4e11..c2090b96e 100644 --- a/src/util/window_info.cpp +++ b/src/util/window_info.cpp @@ -1,9 +1,15 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "window_info.h" #include "common/assert.h" +#include "common/error.h" +#include "common/heap_array.h" +#include "common/log.h" +#include "common/scoped_guard.h" + +Log_SetChannel(WindowInfo); void WindowInfo::SetSurfaceless() { @@ -25,11 +31,80 @@ void WindowInfo::SetSurfaceless() #include "common/windows_headers.h" #include -static bool GetRefreshRateFromDWM(HWND hwnd, float* refresh_rate) +static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd) +{ + // Partially based on Chromium ui/display/win/display_config_helper.cc. + const HMONITOR monitor = MonitorFromWindow(hwnd, 0); + if (!monitor) [[unlikely]] + { + Log_ErrorFmt("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription()); + return std::nullopt; + } + + MONITORINFOEXW mi = {}; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW(monitor, &mi)) + { + Log_ErrorFmt("{}() failed: {}", "GetMonitorInfoW", Error::CreateWin32(GetLastError()).GetDescription()); + return std::nullopt; + } + + DynamicHeapArray path_info; + DynamicHeapArray mode_info; + + // I guess this could fail if it changes inbetween two calls... unlikely. + for (;;) + { + UINT32 path_size = 0, mode_size = 0; + LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size); + if (res != ERROR_SUCCESS) + { + Log_ErrorFmt("{}() failed: {}", "GetDisplayConfigBufferSizes", Error::CreateWin32(res).GetDescription()); + return std::nullopt; + } + + path_info.resize(path_size); + mode_info.resize(mode_size); + res = + QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_size, path_info.data(), &mode_size, mode_info.data(), nullptr); + if (res == ERROR_SUCCESS) + break; + if (res != ERROR_INSUFFICIENT_BUFFER) + { + Log_ErrorFmt("{}() failed: {}", "QueryDisplayConfig", Error::CreateWin32(res).GetDescription()); + return std::nullopt; + } + } + + for (const DISPLAYCONFIG_PATH_INFO& pi : path_info) + { + DISPLAYCONFIG_SOURCE_DEVICE_NAME sdn = {.header = {.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, + .size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME), + .adapterId = pi.sourceInfo.adapterId, + .id = pi.sourceInfo.id}}; + LONG res = DisplayConfigGetDeviceInfo(&sdn.header); + if (res != ERROR_SUCCESS) + { + Log_ErrorFmt("{}() failed: {}", "DisplayConfigGetDeviceInfo", Error::CreateWin32(res).GetDescription()); + continue; + } + + if (std::wcscmp(sdn.viewGdiDeviceName, mi.szDevice) == 0) + { + // Found the monitor! + return static_cast(static_cast(pi.targetInfo.refreshRate.Numerator) / + static_cast(pi.targetInfo.refreshRate.Denominator)); + } + } + + return std::nullopt; +} + +static std::optional GetRefreshRateFromDWM(HWND hwnd) { BOOL composition_enabled; if (FAILED(DwmIsCompositionEnabled(&composition_enabled))) - return false; + return std::nullopt; DWM_TIMING_INFO ti = {}; ti.cbSize = sizeof(ti); @@ -37,20 +112,19 @@ static bool GetRefreshRateFromDWM(HWND hwnd, float* refresh_rate) if (SUCCEEDED(hr)) { if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0) - return false; + return std::nullopt; - *refresh_rate = static_cast(ti.rateRefresh.uiNumerator) / static_cast(ti.rateRefresh.uiDenominator); - return true; + return static_cast(ti.rateRefresh.uiNumerator) / static_cast(ti.rateRefresh.uiDenominator); } - return false; + return std::nullopt; } -static bool GetRefreshRateFromMonitor(HWND hwnd, float* refresh_rate) +static std::optional GetRefreshRateFromMonitor(HWND hwnd) { HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (!mon) - return false; + return std::nullopt; MONITORINFOEXW mi = {}; mi.cbSize = sizeof(mi); @@ -61,38 +135,39 @@ static bool GetRefreshRateFromMonitor(HWND hwnd, float* refresh_rate) // 0/1 are reserved for "defaults". if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1) - { - *refresh_rate = static_cast(dm.dmDisplayFrequency); - return true; - } + return static_cast(dm.dmDisplayFrequency); } - return false; + return std::nullopt; } -bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) +std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) { + std::optional ret; if (wi.type != Type::Win32 || !wi.window_handle) - return false; + return ret; // Try DWM first, then fall back to integer values. const HWND hwnd = static_cast(wi.window_handle); - return GetRefreshRateFromDWM(hwnd, refresh_rate) || GetRefreshRateFromMonitor(hwnd, refresh_rate); + ret = GetRefreshRateFromDisplayConfig(hwnd); + if (!ret.has_value()) + { + ret = GetRefreshRateFromDWM(hwnd); + if (!ret.has_value()) + ret = GetRefreshRateFromMonitor(hwnd); + } + + return ret; } #else #ifdef ENABLE_X11 -#include "common/scoped_guard.h" -#include "common/log.h" - #include #include #include -Log_SetChannel(WindowInfo); - // Helper class for managing X errors namespace { class X11InhibitErrors; @@ -135,12 +210,12 @@ private: }; } // namespace -static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) +static std::optional GetRefreshRateFromXRandR(const WindowInfo& wi) { Display* display = static_cast(wi.display_connection); Window window = static_cast(reinterpret_cast(wi.window_handle)); if (!display || !window) - return false; + return std::nullopt; X11InhibitErrors inhibiter; @@ -148,7 +223,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) if (!res) { Log_ErrorPrint("XRRGetScreenResources() failed"); - return false; + return std::nullopt; } ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); }); @@ -158,7 +233,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) if (num_monitors < 0) { Log_ErrorPrint("XRRGetMonitors() failed"); - return false; + return std::nullopt; } else if (num_monitors > 1) { @@ -169,7 +244,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) if (mi->noutput <= 0) { Log_ErrorPrint("Monitor has no outputs"); - return false; + return std::nullopt; } else if (mi->noutput > 1) { @@ -180,7 +255,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) if (!oi) { Log_ErrorPrint("XRRGetOutputInfo() failed"); - return false; + return std::nullopt; } ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); }); @@ -189,7 +264,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) if (!ci) { Log_ErrorPrint("XRRGetCrtcInfo() failed"); - return false; + return std::nullopt; } ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); }); @@ -206,30 +281,29 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) if (!mode) { Log_ErrorPrintf("Failed to look up mode %d (of %d)", static_cast(ci->mode), res->nmode); - return false; + return std::nullopt; } if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0) { Log_ErrorPrintf("Modeline is invalid: %ld/%d/%d", mode->dotClock, mode->hTotal, mode->vTotal); - return false; + return std::nullopt; } - *refresh_rate = - static_cast(mode->dotClock) / (static_cast(mode->hTotal) * static_cast(mode->vTotal)); - return true; + return static_cast(static_cast(mode->dotClock) / + (static_cast(mode->hTotal) * static_cast(mode->vTotal))); } #endif // ENABLE_X11 -bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) +std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) { #if defined(ENABLE_X11) if (wi.type == WindowInfo::Type::X11) - return GetRefreshRateFromXRandR(wi, refresh_rate); + return GetRefreshRateFromXRandR(wi); #endif - return false; + return std::nullopt; } #endif \ No newline at end of file diff --git a/src/util/window_info.h b/src/util/window_info.h index 2c3f0edbf..4c9be3184 100644 --- a/src/util/window_info.h +++ b/src/util/window_info.h @@ -1,10 +1,13 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once + #include "gpu_texture.h" #include "common/types.h" +#include + // Contains the information required to create a graphics context in a window. struct WindowInfo { @@ -38,5 +41,5 @@ struct WindowInfo // Changes the window to be surfaceless (i.e. no handle/size/etc). void SetSurfaceless(); - static bool QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate); + static std::optional QueryRefreshRateForWindow(const WindowInfo& wi); };