window_info.cpp (8686B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "window_info.h" 5 6 #include "common/assert.h" 7 #include "common/error.h" 8 #include "common/heap_array.h" 9 #include "common/log.h" 10 #include "common/scoped_guard.h" 11 12 Log_SetChannel(WindowInfo); 13 14 void WindowInfo::SetSurfaceless() 15 { 16 type = Type::Surfaceless; 17 window_handle = nullptr; 18 surface_width = 0; 19 surface_height = 0; 20 surface_refresh_rate = 0.0f; 21 surface_scale = 1.0f; 22 surface_format = GPUTexture::Format::Unknown; 23 24 #ifdef __APPLE__ 25 surface_handle = nullptr; 26 #endif 27 } 28 29 #if defined(_WIN32) 30 31 #include "common/windows_headers.h" 32 #include <dwmapi.h> 33 34 static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd) 35 { 36 // Partially based on Chromium ui/display/win/display_config_helper.cc. 37 const HMONITOR monitor = MonitorFromWindow(hwnd, 0); 38 if (!monitor) [[unlikely]] 39 { 40 ERROR_LOG("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription()); 41 return std::nullopt; 42 } 43 44 MONITORINFOEXW mi = {}; 45 mi.cbSize = sizeof(mi); 46 if (!GetMonitorInfoW(monitor, &mi)) 47 { 48 ERROR_LOG("{}() failed: {}", "GetMonitorInfoW", Error::CreateWin32(GetLastError()).GetDescription()); 49 return std::nullopt; 50 } 51 52 DynamicHeapArray<DISPLAYCONFIG_PATH_INFO> path_info; 53 DynamicHeapArray<DISPLAYCONFIG_MODE_INFO> mode_info; 54 55 // I guess this could fail if it changes inbetween two calls... unlikely. 56 for (;;) 57 { 58 UINT32 path_size = 0, mode_size = 0; 59 LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size); 60 if (res != ERROR_SUCCESS) 61 { 62 ERROR_LOG("{}() failed: {}", "GetDisplayConfigBufferSizes", Error::CreateWin32(res).GetDescription()); 63 return std::nullopt; 64 } 65 66 path_info.resize(path_size); 67 mode_info.resize(mode_size); 68 res = 69 QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_size, path_info.data(), &mode_size, mode_info.data(), nullptr); 70 if (res == ERROR_SUCCESS) 71 break; 72 if (res != ERROR_INSUFFICIENT_BUFFER) 73 { 74 ERROR_LOG("{}() failed: {}", "QueryDisplayConfig", Error::CreateWin32(res).GetDescription()); 75 return std::nullopt; 76 } 77 } 78 79 for (const DISPLAYCONFIG_PATH_INFO& pi : path_info) 80 { 81 DISPLAYCONFIG_SOURCE_DEVICE_NAME sdn = {.header = {.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, 82 .size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME), 83 .adapterId = pi.sourceInfo.adapterId, 84 .id = pi.sourceInfo.id}}; 85 LONG res = DisplayConfigGetDeviceInfo(&sdn.header); 86 if (res != ERROR_SUCCESS) 87 { 88 ERROR_LOG("{}() failed: {}", "DisplayConfigGetDeviceInfo", Error::CreateWin32(res).GetDescription()); 89 continue; 90 } 91 92 if (std::wcscmp(sdn.viewGdiDeviceName, mi.szDevice) == 0) 93 { 94 // Found the monitor! 95 return static_cast<float>(static_cast<double>(pi.targetInfo.refreshRate.Numerator) / 96 static_cast<double>(pi.targetInfo.refreshRate.Denominator)); 97 } 98 } 99 100 return std::nullopt; 101 } 102 103 static std::optional<float> GetRefreshRateFromDWM(HWND hwnd) 104 { 105 BOOL composition_enabled; 106 if (FAILED(DwmIsCompositionEnabled(&composition_enabled))) 107 return std::nullopt; 108 109 DWM_TIMING_INFO ti = {}; 110 ti.cbSize = sizeof(ti); 111 HRESULT hr = DwmGetCompositionTimingInfo(nullptr, &ti); 112 if (SUCCEEDED(hr)) 113 { 114 if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0) 115 return std::nullopt; 116 117 return static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator); 118 } 119 120 return std::nullopt; 121 } 122 123 static std::optional<float> GetRefreshRateFromMonitor(HWND hwnd) 124 { 125 HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); 126 if (!mon) 127 return std::nullopt; 128 129 MONITORINFOEXW mi = {}; 130 mi.cbSize = sizeof(mi); 131 if (GetMonitorInfoW(mon, &mi)) 132 { 133 DEVMODEW dm = {}; 134 dm.dmSize = sizeof(dm); 135 136 // 0/1 are reserved for "defaults". 137 if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1) 138 return static_cast<float>(dm.dmDisplayFrequency); 139 } 140 141 return std::nullopt; 142 } 143 144 std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) 145 { 146 std::optional<float> ret; 147 if (wi.type != Type::Win32 || !wi.window_handle) 148 return ret; 149 150 // Try DWM first, then fall back to integer values. 151 const HWND hwnd = static_cast<HWND>(wi.window_handle); 152 ret = GetRefreshRateFromDisplayConfig(hwnd); 153 if (!ret.has_value()) 154 { 155 ret = GetRefreshRateFromDWM(hwnd); 156 if (!ret.has_value()) 157 ret = GetRefreshRateFromMonitor(hwnd); 158 } 159 160 return ret; 161 } 162 163 #elif defined(__APPLE__) 164 165 #include "util/platform_misc.h" 166 167 std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) 168 { 169 if (wi.type == WindowInfo::Type::MacOS) 170 return CocoaTools::GetViewRefreshRate(wi); 171 172 return std::nullopt; 173 } 174 175 #else 176 177 #ifdef ENABLE_X11 178 179 #include <X11/Xlib.h> 180 #include <X11/Xutil.h> 181 #include <X11/extensions/Xrandr.h> 182 183 // Helper class for managing X errors 184 namespace { 185 class X11InhibitErrors; 186 187 static X11InhibitErrors* s_current_error_inhibiter; 188 189 class X11InhibitErrors 190 { 191 public: 192 X11InhibitErrors() 193 { 194 Assert(!s_current_error_inhibiter); 195 m_old_handler = XSetErrorHandler(ErrorHandler); 196 s_current_error_inhibiter = this; 197 } 198 199 ~X11InhibitErrors() 200 { 201 Assert(s_current_error_inhibiter == this); 202 s_current_error_inhibiter = nullptr; 203 XSetErrorHandler(m_old_handler); 204 } 205 206 ALWAYS_INLINE bool HadError() const { return m_had_error; } 207 208 private: 209 static int ErrorHandler(Display* display, XErrorEvent* ee) 210 { 211 char error_string[256] = {}; 212 XGetErrorText(display, ee->error_code, error_string, sizeof(error_string)); 213 WARNING_LOG("X11 Error: {} (Error {} Minor {} Request {})", error_string, ee->error_code, ee->minor_code, 214 ee->request_code); 215 216 s_current_error_inhibiter->m_had_error = true; 217 return 0; 218 } 219 220 XErrorHandler m_old_handler = {}; 221 bool m_had_error = false; 222 }; 223 } // namespace 224 225 static std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi) 226 { 227 Display* display = static_cast<Display*>(wi.display_connection); 228 Window window = static_cast<Window>(reinterpret_cast<uintptr_t>(wi.window_handle)); 229 if (!display || !window) 230 return std::nullopt; 231 232 X11InhibitErrors inhibiter; 233 234 XRRScreenResources* res = XRRGetScreenResources(display, window); 235 if (!res) 236 { 237 ERROR_LOG("XRRGetScreenResources() failed"); 238 return std::nullopt; 239 } 240 241 ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); }); 242 243 int num_monitors; 244 XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors); 245 if (num_monitors < 0) 246 { 247 ERROR_LOG("XRRGetMonitors() failed"); 248 return std::nullopt; 249 } 250 else if (num_monitors > 1) 251 { 252 WARNING_LOG("XRRGetMonitors() returned {} monitors, using first", num_monitors); 253 } 254 255 ScopedGuard mi_guard([mi]() { XRRFreeMonitors(mi); }); 256 if (mi->noutput <= 0) 257 { 258 ERROR_LOG("Monitor has no outputs"); 259 return std::nullopt; 260 } 261 else if (mi->noutput > 1) 262 { 263 WARNING_LOG("Monitor has {} outputs, using first", mi->noutput); 264 } 265 266 XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]); 267 if (!oi) 268 { 269 ERROR_LOG("XRRGetOutputInfo() failed"); 270 return std::nullopt; 271 } 272 273 ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); }); 274 275 XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc); 276 if (!ci) 277 { 278 ERROR_LOG("XRRGetCrtcInfo() failed"); 279 return std::nullopt; 280 } 281 282 ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); }); 283 284 XRRModeInfo* mode = nullptr; 285 for (int i = 0; i < res->nmode; i++) 286 { 287 if (res->modes[i].id == ci->mode) 288 { 289 mode = &res->modes[i]; 290 break; 291 } 292 } 293 if (!mode) 294 { 295 ERROR_LOG("Failed to look up mode {} (of {})", static_cast<int>(ci->mode), res->nmode); 296 return std::nullopt; 297 } 298 299 if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0) 300 { 301 ERROR_LOG("Modeline is invalid: {}/{}/{}", mode->dotClock, mode->hTotal, mode->vTotal); 302 return std::nullopt; 303 } 304 305 return static_cast<float>(static_cast<double>(mode->dotClock) / 306 (static_cast<double>(mode->hTotal) * static_cast<double>(mode->vTotal))); 307 } 308 309 #endif // ENABLE_X11 310 311 std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) 312 { 313 #if defined(ENABLE_X11) 314 if (wi.type == WindowInfo::Type::X11) 315 return GetRefreshRateFromXRandR(wi); 316 #endif 317 318 return std::nullopt; 319 } 320 321 #endif