Add refresh_rate_millihertz for MonitorHandle

This also alters `VideoMode::refresh_rate` to
`VideoMode::refresh_rate_millihertz` which now returns monitor refresh rate in
mHz.
This commit is contained in:
Kirill Chibisov 2022-07-08 13:25:56 +03:00 committed by GitHub
parent e289f30e5d
commit a06bb3f992
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 81 deletions

View file

@ -65,6 +65,8 @@ And please only add new entries to the top of this list, right below the `# Unre
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision.
# 0.26.1 (2022-01-05)

View file

@ -39,8 +39,8 @@ impl Ord for VideoMode {
self.monitor().cmp(&other.monitor()).then(
size.cmp(&other_size)
.then(
self.refresh_rate()
.cmp(&other.refresh_rate())
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
@ -68,12 +68,10 @@ impl VideoMode {
self.video_mode.bit_depth()
}
/// Returns the refresh rate of this video mode. **Note**: the returned
/// refresh rate is an integer approximation, and you shouldn't rely on this
/// value to be exact.
/// Returns the refresh rate of this video mode in mHz.
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.video_mode.refresh_rate()
pub fn refresh_rate_millihertz(&self) -> u32 {
self.video_mode.refresh_rate_millihertz()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
@ -88,10 +86,10 @@ impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} @ {} Hz ({} bpp)",
"{}x{} @ {} mHz ({} bpp)",
self.size().width,
self.size().height,
self.refresh_rate(),
self.refresh_rate_millihertz(),
self.bit_depth()
)
}
@ -141,6 +139,15 @@ impl MonitorHandle {
self.inner.position()
}
/// The monitor refresh rate used by the system.
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// enter fullscreen should be used instead.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz()
}
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](crate::dpi) module for more information.

View file

@ -857,6 +857,11 @@ impl MonitorHandle {
.unwrap_or(1.0)
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
// FIXME no way to get real refrsh rate for now.
None
}
pub fn video_modes(&self) -> impl Iterator<Item = monitor::VideoMode> {
let size = self.size().into();
// FIXME this is not the real refresh rate
@ -865,7 +870,7 @@ impl MonitorHandle {
video_mode: VideoMode {
size,
bit_depth: 32,
refresh_rate: 60,
refresh_rate_millihertz: 60000,
monitor: self.clone(),
},
})
@ -876,7 +881,7 @@ impl MonitorHandle {
pub struct VideoMode {
size: (u32, u32),
bit_depth: u16,
refresh_rate: u16,
refresh_rate_millihertz: u32,
monitor: MonitorHandle,
}
@ -889,8 +894,8 @@ impl VideoMode {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> monitor::MonitorHandle {

View file

@ -17,7 +17,7 @@ use crate::{
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: NativeDisplayMode,
pub(crate) monitor: MonitorHandle,
}
@ -49,7 +49,7 @@ impl Clone for VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate: self.refresh_rate,
refresh_rate_millihertz: self.refresh_rate_millihertz,
screen_mode: self.screen_mode.clone(),
monitor: self.monitor.clone(),
}
@ -59,30 +59,14 @@ impl Clone for VideoMode {
impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let os_capabilities = app_state::os_capabilities();
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
};
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
let size: CGSize = msg_send![screen_mode, size];
let screen_mode: id = msg_send![screen_mode, retain];
let screen_mode = NativeDisplayMode(screen_mode);
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
refresh_rate_millihertz,
screen_mode,
monitor: MonitorHandle::retained_new(uiscreen),
}
@ -96,8 +80,8 @@ impl VideoMode {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {
@ -239,6 +223,10 @@ impl Inner {
}
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(self.uiscreen))
}
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let mut modes = BTreeSet::new();
unsafe {
@ -257,6 +245,30 @@ impl Inner {
}
}
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
let refresh_rate_millihertz: NSInteger = unsafe {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
}
};
refresh_rate_millihertz as u32 * 1000
}
// MonitorHandleExtIOS
impl Inner {
pub fn ui_screen(&self) -> id {

View file

@ -256,6 +256,11 @@ impl MonitorHandle {
x11_or_wayland!(match self; MonitorHandle(m) => m.position())
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz())
}
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64)
@ -287,8 +292,8 @@ impl VideoMode {
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate())
pub fn refresh_rate_millihertz(&self) -> u32 {
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz())
}
#[inline]

View file

@ -167,6 +167,16 @@ impl MonitorHandle {
.into()
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
sctk::output::with_output_info(&self.proxy, |info| {
info.modes
.iter()
.find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32))
})
.flatten()
}
#[inline]
pub fn scale_factor(&self) -> i32 {
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
@ -182,7 +192,7 @@ impl MonitorHandle {
modes.into_iter().map(move |mode| RootVideoMode {
video_mode: PlatformVideoMode::Wayland(VideoMode {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16,
refresh_rate_millihertz: mode.refresh_rate as u32,
bit_depth: 32,
monitor: monitor.clone(),
}),
@ -194,7 +204,7 @@ impl MonitorHandle {
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) monitor: MonitorHandle,
}
@ -210,8 +220,8 @@ impl VideoMode {
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {

View file

@ -1,4 +1,5 @@
use std::os::raw::*;
use std::slice;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
@ -6,7 +7,7 @@ use parking_lot::Mutex;
use super::{
ffi::{
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources,
},
util, XConnection, XError,
};
@ -30,7 +31,7 @@ pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) native_mode: RRMode,
pub(crate) monitor: Option<MonitorHandle>,
}
@ -47,8 +48,8 @@ impl VideoMode {
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
#[inline]
@ -71,6 +72,8 @@ pub struct MonitorHandle {
position: (i32, i32),
/// If the monitor is the primary one
primary: bool,
/// The refresh rate used by monitor.
refresh_rate_millihertz: Option<u32>,
/// The DPI scale factor
pub(crate) scale_factor: f64,
/// Used to determine which windows are on this monitor
@ -105,6 +108,15 @@ impl std::hash::Hash for MonitorHandle {
}
}
#[inline]
pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> {
if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 {
Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32)
} else {
None
}
}
impl MonitorHandle {
fn new(
xconn: &XConnection,
@ -116,10 +128,22 @@ impl MonitorHandle {
let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
// Get the refresh rate of the current video mode.
let current_mode = unsafe { (*crtc).mode };
let screen_modes =
unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) };
let refresh_rate_millihertz = screen_modes
.iter()
.find(|mode| mode.id == current_mode)
.and_then(mode_refresh_rate_millihertz);
let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle {
id,
name,
refresh_rate_millihertz,
scale_factor,
dimensions,
position,
@ -136,6 +160,7 @@ impl MonitorHandle {
scale_factor: 1.0,
dimensions: (1, 1),
position: (0, 0),
refresh_rate_millihertz: None,
primary: true,
rect: util::AaRect::new((0, 0), (1, 1)),
video_modes: Vec::new(),
@ -164,6 +189,10 @@ impl MonitorHandle {
self.position.into()
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.refresh_rate_millihertz
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.scale_factor

View file

@ -4,6 +4,7 @@ use super::{
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use crate::platform_impl::platform::x11::monitor;
use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode};
/// Represents values of `WINIT_HIDPI_FACTOR`.
@ -80,18 +81,13 @@ impl XConnection {
// XRROutputInfo contains an array of mode ids that correspond to
// modes in the array in XRRScreenResources
.filter(|x| output_modes.iter().any(|id| x.id == *id))
.map(|x| {
let refresh_rate = if x.dotClock > 0 && x.hTotal > 0 && x.vTotal > 0 {
x.dotClock as u64 * 1000 / (x.hTotal as u64 * x.vTotal as u64)
} else {
0
};
.map(|mode| {
VideoMode {
size: (x.width, x.height),
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
size: (mode.width, mode.height),
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode)
.unwrap_or(0),
bit_depth: bit_depth as u16,
native_mode: x.id,
native_mode: mode.id,
// This is populated in `MonitorHandle::video_modes` as the
// video mode is returned to the user
monitor: None,

View file

@ -21,7 +21,7 @@ use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) monitor: MonitorHandle,
pub(crate) native_mode: NativeDisplayMode,
}
@ -30,7 +30,7 @@ impl PartialEq for VideoMode {
fn eq(&self, other: &Self) -> bool {
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate == other.refresh_rate
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
&& self.monitor == other.monitor
}
}
@ -41,7 +41,7 @@ impl std::hash::Hash for VideoMode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.size.hash(state);
self.bit_depth.hash(state);
self.refresh_rate.hash(state);
self.refresh_rate_millihertz.hash(state);
self.monitor.hash(state);
}
}
@ -51,7 +51,7 @@ impl std::fmt::Debug for VideoMode {
f.debug_struct("VideoMode")
.field("size", &self.size)
.field("bit_depth", &self.bit_depth)
.field("refresh_rate", &self.refresh_rate)
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
.field("monitor", &self.monitor)
.finish()
}
@ -87,8 +87,8 @@ impl VideoMode {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {
@ -220,8 +220,8 @@ impl MonitorHandle {
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
}
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let cv_refresh_rate = unsafe {
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
unsafe {
let mut display_link = std::ptr::null_mut();
assert_eq!(
ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link),
@ -233,9 +233,12 @@ impl MonitorHandle {
// This value is indefinite if an invalid display link was specified
assert!(time.flags & ffi::kCVTimeIsIndefinite == 0);
time.time_scale as i64 / time.time_value
};
Some((time.time_scale as i64 / time.time_value * 1000) as u32)
}
}
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let refresh_rate_millihertz = self.refresh_rate_millihertz().unwrap_or(0);
let monitor = self.clone();
unsafe {
@ -255,14 +258,15 @@ impl MonitorHandle {
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
let cg_refresh_rate_millihertz =
ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate = if cg_refresh_rate > 0 {
cg_refresh_rate
let refresh_rate_millihertz = if cg_refresh_rate_millihertz > 0 {
(cg_refresh_rate_millihertz * 1000) as u32
} else {
cv_refresh_rate
refresh_rate_millihertz
};
let pixel_encoding =
@ -283,7 +287,7 @@ impl MonitorHandle {
ffi::CGDisplayModeGetPixelWidth(mode) as u32,
ffi::CGDisplayModeGetPixelHeight(mode) as u32,
),
refresh_rate: refresh_rate as u16,
refresh_rate_millihertz,
bit_depth,
monitor: monitor.clone(),
native_mode: NativeDisplayMode(mode),

View file

@ -17,6 +17,10 @@ impl MonitorHandle {
None
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
None
}
pub fn size(&self) -> PhysicalSize<u32> {
PhysicalSize {
width: 0,
@ -41,8 +45,8 @@ impl VideoMode {
unimplemented!();
}
pub fn refresh_rate(&self) -> u16 {
32
pub fn refresh_rate_millihertz(&self) -> u32 {
32000
}
pub fn monitor(&self) -> RootMonitorHandle {

View file

@ -9,8 +9,8 @@ use windows_sys::Win32::{
Graphics::Gdi::{
EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint,
MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT,
DM_PELSWIDTH, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST,
MONITOR_DEFAULTTOPRIMARY,
DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW,
MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY,
},
};
@ -29,7 +29,7 @@ use crate::{
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) monitor: MonitorHandle,
// DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen
pub(crate) native_video_mode: Box<DEVMODEW>,
@ -39,7 +39,7 @@ impl PartialEq for VideoMode {
fn eq(&self, other: &Self) -> bool {
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate == other.refresh_rate
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
&& self.monitor == other.monitor
}
}
@ -50,7 +50,7 @@ impl std::hash::Hash for VideoMode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.size.hash(state);
self.bit_depth.hash(state);
self.refresh_rate.hash(state);
self.refresh_rate_millihertz.hash(state);
self.monitor.hash(state);
}
}
@ -60,7 +60,7 @@ impl std::fmt::Debug for VideoMode {
f.debug_struct("VideoMode")
.field("size", &self.size)
.field("bit_depth", &self.bit_depth)
.field("refresh_rate", &self.refresh_rate)
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
.field("monitor", &self.monitor)
.finish()
}
@ -75,8 +75,8 @@ impl VideoMode {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {
@ -192,6 +192,23 @@ impl MonitorHandle {
}
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
let monitor_info = get_monitor_info(self.0).unwrap();
let device_name = monitor_info.szDevice.as_ptr();
unsafe {
let mut mode: DEVMODEW = mem::zeroed();
mode.dmSize = mem::size_of_val(&mode) as u16;
if EnumDisplaySettingsExW(device_name, ENUM_CURRENT_SETTINGS, &mut mode, 0)
== false.into()
{
None
} else {
Some(mode.dmDisplayFrequency * 1000)
}
}
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor;
@ -233,7 +250,7 @@ impl MonitorHandle {
video_mode: VideoMode {
size: (mode.dmPelsWidth, mode.dmPelsHeight),
bit_depth: mode.dmBitsPerPel as u16,
refresh_rate: mode.dmDisplayFrequency as u16,
refresh_rate_millihertz: mode.dmDisplayFrequency as u32 * 1000,
monitor: self.clone(),
native_video_mode: Box::new(mode),
},