duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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