GS: Improve vsync mode selection

All games use mailbox/triple buffering. Except when you enable sync to
host refresh, in which case FIFO/double buffering is used.

This means vsync enabled will ever tear, but at the same time, never
drop to 30fps on a missed frame due to frame rate differences.

To have the "best of both worlds", you should enable vsync and sync to
host refresh. Previously, this resulted in additional input lag, since
the host vsync would drive the EE frame timing. Now, this behaviour is
disabled by default, unless you enable "Use Host VSync Timing".
This commit is contained in:
Stenzek
2024-05-25 14:06:50 +10:00
committed by Connor McLaughlin
parent 82fbf34f5b
commit c7a21a60cf
40 changed files with 707 additions and 460 deletions
+119 -32
View File
@@ -1,19 +1,90 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "WindowInfo.h"
#include "Console.h"
#include "Error.h"
#include "HeapArray.h"
#if defined(_WIN32)
#include "RedtapeWindows.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]]
{
ERROR_LOG("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription());
return std::nullopt;
}
MONITORINFOEXW mi = {};
mi.cbSize = sizeof(mi);
if (!GetMonitorInfoW(monitor, &mi))
{
ERROR_LOG("{}() 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)
{
ERROR_LOG("{}() 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)
{
ERROR_LOG("{}() 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)
{
ERROR_LOG("{}() 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);
@@ -21,20 +92,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);
@@ -45,23 +115,41 @@ 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;
}
#elif defined(__APPLE__)
#include "common/CocoaTools.h"
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
{
if (wi.type == WindowInfo::Type::MacOS)
return CocoaTools::GetViewRefreshRate(wi);
return std::nullopt;
}
#else
@@ -72,18 +160,18 @@ bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_
#include <X11/extensions/Xrandr.h>
#include <X11/Xlib.h>
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;
XRRScreenResources* res = XRRGetScreenResources(display, window);
if (!res)
{
Console.Error("(GetRefreshRateFromXRandR) XRRGetScreenResources() failed");
return false;
return std::nullopt;
}
ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); });
@@ -93,7 +181,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate)
if (num_monitors < 0)
{
Console.Error("(GetRefreshRateFromXRandR) XRRGetMonitors() failed");
return false;
return std::nullopt;
}
else if (num_monitors > 1)
{
@@ -104,7 +192,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate)
if (mi->noutput <= 0)
{
Console.Error("(GetRefreshRateFromXRandR) Monitor has no outputs");
return false;
return std::nullopt;
}
else if (mi->noutput > 1)
{
@@ -115,7 +203,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate)
if (!oi)
{
Console.Error("(GetRefreshRateFromXRandR) XRRGetOutputInfo() failed");
return false;
return std::nullopt;
}
ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); });
@@ -124,7 +212,7 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate)
if (!ci)
{
Console.Error("(GetRefreshRateFromXRandR) XRRGetCrtcInfo() failed");
return false;
return std::nullopt;
}
ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); });
@@ -141,30 +229,29 @@ static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate)
if (!mode)
{
Console.Error("(GetRefreshRateFromXRandR) 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)
{
Console.Error("(GetRefreshRateFromXRandR) 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 // X11_API
bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate)
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
{
#if defined(X11_API)
if (wi.type == WindowInfo::Type::X11)
return GetRefreshRateFromXRandR(wi, refresh_rate);
return GetRefreshRateFromXRandR(wi);
#endif
return false;
return std::nullopt;
}
#endif