HostDisplay: Support refresh rate queries on all platforms except Mac

This commit is contained in:
Connor McLaughlin
2021-04-03 00:55:09 +10:00
parent e94c68e874
commit 924756860e
26 changed files with 344 additions and 208 deletions

View File

@ -108,6 +108,8 @@ add_library(common
vulkan/util.h
wav_writer.cpp
wav_writer.h
window_info.cpp
window_info.h
)
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
@ -147,8 +149,8 @@ if(USE_X11)
gl/x11_window.h
)
target_compile_definitions(common PRIVATE "-DUSE_X11=1")
target_include_directories(common PRIVATE "${X11_INCLUDE_DIR}")
target_link_libraries(common PRIVATE "${X11_LIBRARIES}")
target_include_directories(common PRIVATE "${X11_INCLUDE_DIR}" "${X11_Xrandr_INCLUDE_PATH}")
target_link_libraries(common PRIVATE "${X11_LIBRARIES}" "${X11_Xrandr_LIB}")
endif()
if(USE_DRMKMS)

View File

@ -189,6 +189,7 @@
<ClCompile Include="vulkan\util.cpp" />
<ClCompile Include="wav_writer.cpp" />
<ClCompile Include="win32_progress_callback.cpp" />
<ClCompile Include="window_info.cpp" />
</ItemGroup>
<ItemGroup>
<Natvis Include="bitfield.natvis" />

View File

@ -217,6 +217,7 @@
<ClCompile Include="cd_image_pbp.cpp" />
<ClCompile Include="error.cpp" />
<ClCompile Include="cd_image_m3u.cpp" />
<ClCompile Include="window_info.cpp" />
</ItemGroup>
<ItemGroup>
<Natvis Include="bitfield.natvis" />

View File

@ -17,6 +17,11 @@ public:
int GetCardFD() const { return m_card_fd; }
u32 GetWidth() const { return m_mode->hdisplay; }
u32 GetHeight() const { return m_mode->vdisplay; }
float GetRefreshRate() const
{
return (static_cast<float>(m_mode->clock) * 1000.0f) /
(static_cast<float>(m_mode->htotal) * static_cast<float>(m_mode->vtotal));
}
u32 GetModeCount() const { return m_connector->count_modes; }
u32 GetModeWidth(u32 i) const { return m_connector->modes[i].hdisplay; }

View File

@ -43,7 +43,7 @@ std::unique_ptr<Context> ContextEGLGBM::Create(const WindowInfo& wi, const Versi
size_t num_versions_to_try)
{
std::unique_ptr<ContextEGLGBM> context = std::make_unique<ContextEGLGBM>(wi);
if (!context->CreateDisplay(wi) || !context->CreateGBMDevice() ||
if (!context->CreateDisplay() || !context->CreateGBMDevice() ||
!context->Initialize(versions_to_try, num_versions_to_try))
{
return nullptr;
@ -81,9 +81,15 @@ bool ContextEGLGBM::CreateGBMDevice()
return true;
}
bool ContextEGLGBM::CreateDisplay(const WindowInfo& wi)
bool ContextEGLGBM::CreateDisplay()
{
return m_drm_display.Initialize(wi.surface_width, wi.surface_height, wi.surface_refresh_rate);
if (!m_drm_display.Initialize(m_wi.surface_width, m_wi.surface_height, m_wi.surface_refresh_rate))
return false;
m_wi.surface_width = m_drm_display.GetWidth();
m_wi.surface_height = m_drm_display.GetHeight();
m_wi.surface_refresh_rate = m_drm_display.GetRefreshRate();
return true;
}
bool ContextEGLGBM::SetDisplay()

View File

@ -44,7 +44,7 @@ private:
u32 fb_id;
};
bool CreateDisplay(const WindowInfo& wi);
bool CreateDisplay();
bool CreateGBMDevice();
Buffer* LockFrontBuffer();

View File

@ -353,7 +353,7 @@ bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::uniqu
wi_copy = *wi;
if (enable_surface &&
(surface = SwapChain::CreateVulkanSurface(instance, gpus[gpu_index], wi_copy)) == VK_NULL_HANDLE)
(surface = SwapChain::CreateVulkanSurface(instance, gpus[gpu_index], &wi_copy)) == VK_NULL_HANDLE)
{
vkDestroyInstance(instance, nullptr);
Vulkan::UnloadVulkanLibrary();

View File

@ -20,9 +20,9 @@ Log_SetChannel(Vulkan::SwapChain);
#if defined(__APPLE__)
#include <objc/message.h>
static bool CreateMetalLayer(WindowInfo& wi)
static bool CreateMetalLayer(WindowInfo* wi)
{
id view = reinterpret_cast<id>(wi.window_handle);
id view = reinterpret_cast<id>(wi->window_handle);
Class clsCAMetalLayer = objc_getClass("CAMetalLayer");
if (!clsCAMetalLayer)
@ -55,28 +55,28 @@ static bool CreateMetalLayer(WindowInfo& wi)
reinterpret_cast<void (*)(id, SEL, double)>(objc_msgSend)(layer, sel_getUid("setContentsScale:"), factor);
// Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread.
wi.surface_handle = layer;
wi->surface_handle = layer;
return true;
}
static void DestroyMetalLayer(WindowInfo& wi)
static void DestroyMetalLayer(WindowInfo* wi)
{
id view = reinterpret_cast<id>(wi.window_handle);
id layer = reinterpret_cast<id>(wi.surface_handle);
id view = reinterpret_cast<id>(wi->window_handle);
id layer = reinterpret_cast<id>(wi->surface_handle);
if (layer == nil)
return;
reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend)(view, sel_getUid("setLayer:"), nil);
reinterpret_cast<void (*)(id, SEL, BOOL)>(objc_msgSend)(view, sel_getUid("setWantsLayer:"), NO);
reinterpret_cast<void (*)(id, SEL)>(objc_msgSend)(layer, sel_getUid("release"));
wi.surface_handle = nullptr;
wi->surface_handle = nullptr;
}
#endif
namespace Vulkan {
SwapChain::SwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync)
: m_wi(wi), m_vsync_enabled(vsync), m_surface(surface)
: m_window_info(wi), m_surface(surface), m_vsync_enabled(vsync)
{
}
@ -88,9 +88,9 @@ SwapChain::~SwapChain()
DestroySurface();
}
static VkSurfaceKHR CreateDisplaySurface(VkInstance instance, VkPhysicalDevice physical_device, const WindowInfo& wi)
static VkSurfaceKHR CreateDisplaySurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi)
{
Log_InfoPrintf("Trying to create a VK_KHR_display surface of %ux%u", wi.surface_width, wi.surface_height);
Log_InfoPrintf("Trying to create a VK_KHR_display surface of %ux%u", wi->surface_width, wi->surface_height);
u32 num_displays;
VkResult res = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_device, &num_displays, nullptr);
@ -137,9 +137,9 @@ static VkSurfaceKHR CreateDisplaySurface(VkInstance instance, VkPhysicalDevice p
refresh_rate);
if (!matched_mode &&
((wi.surface_width == 0 && wi.surface_height == 0) ||
(mode.parameters.visibleRegion.width == wi.surface_width && mode.parameters.visibleRegion.height &&
(wi.surface_refresh_rate == 0.0f || std::abs(refresh_rate - wi.surface_refresh_rate) < 0.1f))))
((wi->surface_width == 0 && wi->surface_height == 0) ||
(mode.parameters.visibleRegion.width == wi->surface_width && mode.parameters.visibleRegion.height &&
(wi->surface_refresh_rate == 0.0f || std::abs(refresh_rate - wi->surface_refresh_rate) < 0.1f))))
{
matched_mode = &mode;
}
@ -219,6 +219,7 @@ static VkSurfaceKHR CreateDisplaySurface(VkInstance instance, VkPhysicalDevice p
continue;
}
wi->surface_refresh_rate = static_cast<float>(matched_mode->parameters.refreshRate) / 1000.0f;
return surface;
}
@ -286,17 +287,17 @@ static std::vector<SwapChain::FullscreenModeInfo> GetDisplayModes(VkInstance ins
return result;
}
VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo& wi)
VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi)
{
#if defined(VK_USE_PLATFORM_WIN32_KHR)
if (wi.type == WindowInfo::Type::Win32)
if (wi->type == WindowInfo::Type::Win32)
{
VkWin32SurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkWin32SurfaceCreateFlagsKHR flags
nullptr, // HINSTANCE hinstance
reinterpret_cast<HWND>(wi.window_handle) // HWND hwnd
reinterpret_cast<HWND>(wi->window_handle) // HWND hwnd
};
VkSurfaceKHR surface;
@ -312,14 +313,14 @@ VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevic
#endif
#if defined(VK_USE_PLATFORM_XLIB_KHR)
if (wi.type == WindowInfo::Type::X11)
if (wi->type == WindowInfo::Type::X11)
{
VkXlibSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkXlibSurfaceCreateFlagsKHR flags
static_cast<Display*>(wi.display_connection), // Display* dpy
reinterpret_cast<Window>(wi.window_handle) // Window window
static_cast<Display*>(wi->display_connection), // Display* dpy
reinterpret_cast<Window>(wi->window_handle) // Window window
};
VkSurfaceKHR surface;
@ -335,11 +336,11 @@ VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevic
#endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
if (wi.type == WindowInfo::Type::Wayland)
if (wi->type == WindowInfo::Type::Wayland)
{
VkWaylandSurfaceCreateInfoKHR surface_create_info = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
static_cast<struct wl_display*>(wi.display_connection),
static_cast<struct wl_surface*>(wi.window_handle)};
static_cast<struct wl_display*>(wi->display_connection),
static_cast<struct wl_surface*>(wi->window_handle)};
VkSurfaceKHR surface;
VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
@ -354,13 +355,13 @@ VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevic
#endif
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
if (wi.type == WindowInfo::Type::Android)
if (wi->type == WindowInfo::Type::Android)
{
VkAndroidSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkAndroidSurfaceCreateFlagsKHR flags
reinterpret_cast<ANativeWindow*>(wi.window_handle) // ANativeWindow* window
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkAndroidSurfaceCreateFlagsKHR flags
reinterpret_cast<ANativeWindow*>(wi->window_handle) // ANativeWindow* window
};
VkSurfaceKHR surface;
@ -376,13 +377,13 @@ VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevic
#endif
#if defined(VK_USE_PLATFORM_METAL_EXT)
if (wi.type == WindowInfo::Type::MacOS)
if (wi->type == WindowInfo::Type::MacOS)
{
if (!wi.surface_handle && !CreateMetalLayer(wi))
if (!wi->surface_handle && !CreateMetalLayer(wi))
return VK_NULL_HANDLE;
VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0,
static_cast<const CAMetalLayer*>(wi.surface_handle)};
static_cast<const CAMetalLayer*>(wi->surface_handle)};
VkSurfaceKHR surface;
VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface);
@ -395,10 +396,10 @@ VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevic
return surface;
}
#elif defined(VK_USE_PLATFORM_MACOS_MVK)
if (wi.type == WindowInfo::Type::MacOS)
if (wi->type == WindowInfo::Type::MacOS)
{
VkMacOSSurfaceCreateInfoMVK surface_create_info = {VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, nullptr, 0,
wi.window_handle};
wi->window_handle};
VkSurfaceKHR surface;
VkResult res = vkCreateMacOSSurfaceMVK(instance, &surface_create_info, nullptr, &surface);
@ -412,18 +413,18 @@ VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevic
}
#endif
if (wi.type == WindowInfo::Type::Display)
if (wi->type == WindowInfo::Type::Display)
return CreateDisplaySurface(instance, physical_device, wi);
return VK_NULL_HANDLE;
}
void SwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo& wi, VkSurfaceKHR surface)
void SwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface)
{
vkDestroySurfaceKHR(g_vulkan_context->GetVulkanInstance(), surface, nullptr);
#if defined(__APPLE__)
if (wi.type == WindowInfo::Type::MacOS && wi.surface_handle)
if (wi->type == WindowInfo::Type::MacOS && wi->surface_handle)
DestroyMetalLayer(wi);
#endif
}
@ -567,8 +568,8 @@ bool SwapChain::CreateSwapChain()
if (size.width == UINT32_MAX)
#endif
{
size.width = m_wi.surface_width;
size.height = m_wi.surface_height;
size.width = m_window_info.surface_width;
size.height = m_window_info.surface_height;
}
size.width =
std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
@ -637,8 +638,8 @@ bool SwapChain::CreateSwapChain()
if (old_swap_chain != VK_NULL_HANDLE)
vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr);
m_width = size.width;
m_height = size.height;
m_window_info.surface_width = size.width;
m_window_info.surface_height = size.height;
return true;
}
@ -675,8 +676,8 @@ bool SwapChain::SetupSwapChainImages()
image.image = images[i];
// Create texture object, which creates a view of the backbuffer
if (!image.texture.Adopt(image.image, VK_IMAGE_VIEW_TYPE_2D, m_width, m_height, 1, 1, m_surface_format.format,
VK_SAMPLE_COUNT_1_BIT))
if (!image.texture.Adopt(image.image, VK_IMAGE_VIEW_TYPE_2D, m_window_info.surface_width,
m_window_info.surface_height, 1, 1, m_surface_format.format, VK_SAMPLE_COUNT_1_BIT))
{
return false;
}
@ -723,8 +724,8 @@ bool SwapChain::ResizeSwapChain(u32 new_width /* = 0 */, u32 new_height /* = 0 *
if (new_width != 0 && new_height != 0)
{
m_wi.surface_width = new_width;
m_wi.surface_height = new_height;
m_window_info.surface_width = new_width;
m_window_info.surface_height = new_height;
}
if (!CreateSwapChain() || !SetupSwapChainImages() || !CreateSemaphores())
@ -769,8 +770,9 @@ bool SwapChain::RecreateSurface(const WindowInfo& new_wi)
DestroySurface();
// Re-create the surface with the new native handle
m_wi = new_wi;
m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), m_wi);
m_window_info = new_wi;
m_surface =
CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), &m_window_info);
if (m_surface == VK_NULL_HANDLE)
return false;
@ -799,7 +801,7 @@ bool SwapChain::RecreateSurface(const WindowInfo& new_wi)
void SwapChain::DestroySurface()
{
DestroyVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wi, m_surface);
DestroyVulkanSurface(g_vulkan_context->GetVulkanInstance(), &m_window_info, m_surface);
m_surface = VK_NULL_HANDLE;
}

View File

@ -21,10 +21,10 @@ public:
~SwapChain();
// Creates a vulkan-renderable surface for the specified window handle.
static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo& wi);
static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi);
// Destroys a previously-created surface.
static void DestroyVulkanSurface(VkInstance instance, WindowInfo& wi, VkSurfaceKHR surface);
static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface);
// Enumerates fullscreen modes for window info.
struct FullscreenModeInfo
@ -44,8 +44,9 @@ public:
ALWAYS_INLINE VkFormat GetTextureFormat() const { return m_surface_format.format; }
ALWAYS_INLINE bool IsVSyncEnabled() const { return m_vsync_enabled; }
ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
ALWAYS_INLINE u32 GetWidth() const { return m_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_height; }
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
ALWAYS_INLINE u32 GetCurrentImageIndex() const { return m_current_image; }
ALWAYS_INLINE u32 GetImageCount() const { return static_cast<u32>(m_images.size()); }
ALWAYS_INLINE VkImage GetCurrentImage() const { return m_images[m_current_image].image; }
@ -87,10 +88,7 @@ private:
VkFramebuffer framebuffer;
};
u32 m_width = 0;
u32 m_height = 0;
WindowInfo m_wi;
bool m_vsync_enabled = false;
WindowInfo m_window_info;
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
VkSurfaceFormatKHR m_surface_format = {};
@ -105,6 +103,7 @@ private:
VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE;
std::vector<SwapChainImage> m_images;
u32 m_current_image = 0;
bool m_vsync_enabled = false;
};
} // namespace Vulkan

191
src/common/window_info.cpp Normal file
View File

@ -0,0 +1,191 @@
#include "window_info.h"
#include "common/log.h"
Log_SetChannel(WindowInfo);
#if defined(_WIN32)
#include "common/windows_headers.h"
#include <dwmapi.h>
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 = LoadLibrary("dwmapi.dll");
if (dwm_module)
{
std::atexit([]() {
FreeLibrary(dwm_module);
dwm_module = nullptr;
});
is_composition_enabled =
reinterpret_cast<decltype(is_composition_enabled)>(GetProcAddress(dwm_module, "DwmIsCompositionEnabled"));
get_timing_info =
reinterpret_cast<decltype(get_timing_info)>(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<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(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<float>(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<HWND>(wi.window_handle);
return GetRefreshRateFromDWM(hwnd, refresh_rate) || GetRefreshRateFromMonitor(hwnd, refresh_rate);
}
#elif defined(__linux__)
#ifdef USE_X11
#include "common/scope_guard.h"
#include "gl/x11_window.h"
#include <X11/extensions/Xrandr.h>
static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate)
{
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;
GL::X11InhibitErrors inhibiter;
XRRScreenResources* res = XRRGetScreenResources(display, window);
if (!res)
{
Log_ErrorPrint("XRRGetScreenResources() failed");
return false;
}
Common::ScopeGuard res_guard([res]() { XRRFreeScreenResources(res); });
int num_monitors;
XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors);
if (num_monitors < 0)
{
Log_ErrorPrint("XRRGetMonitors() failed");
return false;
}
else if (num_monitors > 1)
{
Log_WarningPrintf("XRRGetMonitors() returned %d monitors, using first", num_monitors);
}
Common::ScopeGuard mi_guard([mi]() { XRRFreeMonitors(mi); });
if (mi->noutput <= 0)
{
Log_ErrorPrint("Monitor has no outputs");
return false;
}
else if (mi->noutput > 1)
{
Log_WarningPrintf("Monitor has %d outputs, using first", mi->noutput);
}
XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]);
if (!oi)
{
Log_ErrorPrint("XRRGetOutputInfo() failed");
return false;
}
Common::ScopeGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); });
XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc);
if (!ci)
{
Log_ErrorPrint("XRRGetCrtcInfo() failed");
return false;
}
Common::ScopeGuard 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)
{
Log_ErrorPrintf("Failed to look up mode %d (of %d)", static_cast<int>(ci->mode), res->nmode);
return false;
}
if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0)
{
Log_ErrorPrintf("Modeline is invalid: %ld/%d/%d", mode->dotClock, mode->hTotal, mode->vTotal);
return false;
}
*refresh_rate =
static_cast<double>(mode->dotClock) / (static_cast<double>(mode->hTotal) * static_cast<double>(mode->vTotal));
return true;
}
#endif // USE_X11
bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate)
{
#if defined(USE_X11)
if (wi.type == WindowInfo::Type::X11)
return GetRefreshRateFromXRandR(wi, refresh_rate);
#endif
return false;
}
#endif

View File

@ -38,4 +38,6 @@ struct WindowInfo
#ifdef __APPLE__
void* surface_handle = nullptr;
#endif
static bool QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate);
};