WindowInfo: Get refresh rate from monitor config

DwmGetCompositionTimingInfo() returns a noisy refresh rate, at least on
Win11 22H2.
This commit is contained in:
Stenzek
2024-05-22 22:16:29 +10:00
parent f9b58c4077
commit d9cc80c7f9
9 changed files with 131 additions and 59 deletions

View File

@ -1,9 +1,15 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// 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 <dwmapi.h>
static bool GetRefreshRateFromDWM(HWND hwnd, float* refresh_rate)
static std::optional<float> 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<DISPLAYCONFIG_PATH_INFO> path_info;
DynamicHeapArray<DISPLAYCONFIG_MODE_INFO> 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<float>(static_cast<double>(pi.targetInfo.refreshRate.Numerator) /
static_cast<double>(pi.targetInfo.refreshRate.Denominator));
}
}
return std::nullopt;
}
static std::optional<float> 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<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator);
return true;
return static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator);
}
return false;
return std::nullopt;
}
static bool GetRefreshRateFromMonitor(HWND hwnd, float* refresh_rate)
static std::optional<float> 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<float>(dm.dmDisplayFrequency);
return true;
}
return static_cast<float>(dm.dmDisplayFrequency);
}
return false;
return std::nullopt;
}
bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate)
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
{
std::optional<float> 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<HWND>(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 <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrandr.h>
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<float> GetRefreshRateFromXRandR(const WindowInfo& wi)
{
Display* display = static_cast<Display*>(wi.display_connection);
Window window = static_cast<Window>(reinterpret_cast<uintptr_t>(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<int>(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<double>(mode->dotClock) / (static_cast<double>(mode->hTotal) * static_cast<double>(mode->vTotal));
return true;
return static_cast<float>(static_cast<double>(mode->dotClock) /
(static_cast<double>(mode->hTotal) * static_cast<double>(mode->vTotal)));
}
#endif // ENABLE_X11
bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate)
std::optional<float> 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