From fadd97c021a6a459e9214423c773c500faf26182 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 18 Jul 2021 13:11:33 +1000 Subject: [PATCH] Common: Add WindowInfo --- cmake/SearchForStuff.cmake | 1 + common/CMakeLists.txt | 7 ++ common/WindowInfo.cpp | 205 ++++++++++++++++++++++++++++++++++ common/WindowInfo.h | 54 +++++++++ common/common.vcxproj | 2 + common/common.vcxproj.filters | 8 +- 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 common/WindowInfo.cpp create mode 100644 common/WindowInfo.h diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake index 90e570424e..04da0f03aa 100644 --- a/cmake/SearchForStuff.cmake +++ b/cmake/SearchForStuff.cmake @@ -123,6 +123,7 @@ else() check_lib(EGL EGL EGL/egl.h) check_lib(X11_XCB X11-xcb X11/Xlib-xcb.h) check_lib(XCB xcb xcb/xcb.h) + check_lib(XRANDR xrandr) if(Linux) check_lib(AIO aio libaio.h) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 21ff56415c..2a405ae5da 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(common PRIVATE StringHelpers.cpp StringUtil.cpp ThreadTools.cpp + WindowInfo.cpp emitter/bmi.cpp emitter/cpudetect.cpp emitter/fpu.cpp @@ -85,6 +86,7 @@ target_sources(common PRIVATE StringUtil.h Threading.h TraceLog.h + WindowInfo.h wxBaseTools.h emitter/cpudetect_internal.h emitter/implement/dwshift.h @@ -121,6 +123,11 @@ if(WIN32) enable_language(ASM_MASM) target_sources(common PRIVATE FastJmp.asm) target_link_libraries(common PUBLIC pthreads4w Winmm.lib) +elseif(NOT APPLE) + if(TARGET PkgConfig::XRANDR) + target_link_libraries(common PRIVATE PkgConfig::XRANDR) + target_compile_definitions(common PRIVATE "HAS_XRANDR=1") + endif() endif() target_link_libraries(common PRIVATE ${LIBC_LIBRARIES} PUBLIC wxWidgets::all) diff --git a/common/WindowInfo.cpp b/common/WindowInfo.cpp new file mode 100644 index 0000000000..f4ffd16875 --- /dev/null +++ b/common/WindowInfo.cpp @@ -0,0 +1,205 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "WindowInfo.h" +#include "Console.h" + +#if defined(_WIN32) && !defined(_UWP) + +#include "RedtapeWindows.h" +#include + +static bool GetRefreshRateFromDWM(HWND hwnd, float* refresh_rate) +{ + static HMODULE dwm_module = nullptr; + static HRESULT(STDAPICALLTYPE * is_composition_enabled)(BOOL * pfEnabled) = nullptr; + static HRESULT(STDAPICALLTYPE * get_timing_info)(HWND hwnd, DWM_TIMING_INFO * pTimingInfo) = nullptr; + static bool load_tried = false; + if (!load_tried) + { + load_tried = true; + dwm_module = LoadLibraryW(L"dwmapi.dll"); + if (dwm_module) + { + std::atexit([]() { + FreeLibrary(dwm_module); + dwm_module = nullptr; + }); + is_composition_enabled = + reinterpret_cast(GetProcAddress(dwm_module, "DwmIsCompositionEnabled")); + get_timing_info = + reinterpret_cast(GetProcAddress(dwm_module, "DwmGetCompositionTimingInfo")); + } + } + + BOOL composition_enabled; + if (!is_composition_enabled || FAILED(is_composition_enabled(&composition_enabled) || !get_timing_info)) + return false; + + DWM_TIMING_INFO ti = {}; + ti.cbSize = sizeof(ti); + HRESULT hr = get_timing_info(nullptr, &ti); + if (SUCCEEDED(hr)) + { + if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0) + return false; + + *refresh_rate = static_cast(ti.rateRefresh.uiNumerator) / static_cast(ti.rateRefresh.uiDenominator); + return true; + } + + return false; +} + +static bool GetRefreshRateFromMonitor(HWND hwnd, float* refresh_rate) +{ + HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if (!mon) + return false; + + MONITORINFOEXW mi = {}; + mi.cbSize = sizeof(mi); + if (GetMonitorInfoW(mon, &mi)) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + // 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 false; +} + +bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) +{ + if (wi.type != Type::Win32 || !wi.window_handle) + return false; + + // 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); +} + +#else + +#if defined(X11_API) && defined(HAS_XRANDR) + +#include "common/ScopedGuard.h" +#include +#include + +static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) +{ + Display* display = static_cast(wi.display_connection); + Window window = static_cast(reinterpret_cast(wi.window_handle)); + if (!display || !window) + return false; + + XRRScreenResources* res = XRRGetScreenResources(display, window); + if (!res) + { + Console.Error("(GetRefreshRateFromXRandR) XRRGetScreenResources() failed"); + return false; + } + + ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); }); + + int num_monitors; + XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors); + if (num_monitors < 0) + { + Console.Error("(GetRefreshRateFromXRandR) XRRGetMonitors() failed"); + return false; + } + else if (num_monitors > 1) + { + Console.Warning("(GetRefreshRateFromXRandR) XRRGetMonitors() returned %d monitors, using first", num_monitors); + } + + ScopedGuard mi_guard([mi]() { XRRFreeMonitors(mi); }); + if (mi->noutput <= 0) + { + Console.Error("(GetRefreshRateFromXRandR) Monitor has no outputs"); + return false; + } + else if (mi->noutput > 1) + { + Console.Warning("(GetRefreshRateFromXRandR) Monitor has %d outputs, using first", mi->noutput); + } + + XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]); + if (!oi) + { + Console.Error("(GetRefreshRateFromXRandR) XRRGetOutputInfo() failed"); + return false; + } + + ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); }); + + XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc); + if (!ci) + { + Console.Error("(GetRefreshRateFromXRandR) XRRGetCrtcInfo() failed"); + return false; + } + + ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); }); + + XRRModeInfo* mode = nullptr; + for (int i = 0; i < res->nmode; i++) + { + if (res->modes[i].id == ci->mode) + { + mode = &res->modes[i]; + break; + } + } + if (!mode) + { + Console.Error("(GetRefreshRateFromXRandR) Failed to look up mode %d (of %d)", static_cast(ci->mode), res->nmode); + return false; + } + + 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; + } + + *refresh_rate = + static_cast(mode->dotClock) / (static_cast(mode->hTotal) * static_cast(mode->vTotal)); + return true; +} + +#endif // X11_API && defined(HAS_XRANDR) + +bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) +{ +#if defined(X11_API) && defined(HAS_XRANDR) + if (wi.type == WindowInfo::Type::X11) + return GetRefreshRateFromXRandR(wi, refresh_rate); +#endif + + return false; +} + +#endif diff --git a/common/WindowInfo.h b/common/WindowInfo.h new file mode 100644 index 0000000000..0d1e80d268 --- /dev/null +++ b/common/WindowInfo.h @@ -0,0 +1,54 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "Pcsx2Defs.h" + +/// Contains the information required to create a graphics context in a window. +struct WindowInfo +{ + enum class Type + { + Surfaceless, + Win32, + X11, + Wayland, + MacOS + }; + + /// The type of the surface. Surfaceless indicates it will not be displayed on screen at all. + Type type = Type::Surfaceless; + + /// Connection to the display server. On most platforms except X11/Wayland, this is implicit and null. + void* display_connection = nullptr; + + /// Abstract handle to the window. This depends on the surface type. + void* window_handle = nullptr; + + /// Width of the surface in pixels. + u32 surface_width = 0; + + /// Height of the surface in pixels. + u32 surface_height = 0; + + /// DPI scale for the surface. + float surface_scale = 1.0f; + + /// Refresh rate of the surface, if available. + float surface_refresh_rate = 0.0f; + + /// Returns the host's refresh rate for the given window, if available. + static bool QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate); +}; diff --git a/common/common.vcxproj b/common/common.vcxproj index 09c4f331bf..fc7b90cb42 100644 --- a/common/common.vcxproj +++ b/common/common.vcxproj @@ -54,6 +54,7 @@ + @@ -117,6 +118,7 @@ + diff --git a/common/common.vcxproj.filters b/common/common.vcxproj.filters index c134c67756..b296170392 100644 --- a/common/common.vcxproj.filters +++ b/common/common.vcxproj.filters @@ -118,6 +118,9 @@ Source Files + + Source Files + @@ -276,6 +279,9 @@ Header Files + + Header Files + @@ -295,4 +301,4 @@ Source Files - \ No newline at end of file +