mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
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:
committed by
Connor McLaughlin
parent
82fbf34f5b
commit
c7a21a60cf
+119
-32
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user