kms: Try to calculate dpi and scale factor (Take 2)

Co-authored-by: Jeremy Soller <jackpot51@gmail.com>
This commit is contained in:
Victoria Brekenfeld 2024-08-15 15:10:40 -06:00 committed by GitHub
parent bad8837d19
commit 8d2cc05f03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -15,7 +15,7 @@ use smithay::{
egl::{context::ContextPriority, EGLContext, EGLDevice, EGLDisplay},
session::Session,
},
output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel},
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
reexports::{
calloop::{LoopHandle, RegistrationToken},
drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags},
@ -625,6 +625,11 @@ fn populate_modes(
else {
anyhow::bail!("No mode found");
};
let scale = conn_info
.size()
.map(|size| calculate_scale(conn_info.interface(), size, mode.size()))
.unwrap_or(1.0);
let refresh_rate = drm_helpers::calculate_refresh_rate(mode);
let output_mode = OutputMode {
size: (mode.size().0 as i32, mode.size().1 as i32).into(),
@ -644,7 +649,7 @@ fn populate_modes(
Some(output_mode),
// TODO: Readout property for monitor rotation
Some(Transform::Normal),
None,
Some(Scale::Fractional(scale)),
Some(Point::from((position.0 as i32, position.1 as i32))),
);
@ -657,8 +662,164 @@ fn populate_modes(
mode: ((output_mode.size.w, output_mode.size.h), Some(refresh_rate)),
position,
max_bpc,
scale,
..std::mem::take(&mut *output_config)
};
Ok(())
}
pub fn calculate_scale(
interface: connector::Interface,
monitor_size_mm: (u32, u32),
resolution: (u16, u16),
) -> f64 {
let (w_mm, h_mm) = monitor_size_mm;
let (w_px, h_px) = resolution;
if w_mm == 0 || h_mm == 0 {
// possibly projector, but could just be some no-brand display
return 1.0;
}
let (w_in, h_in) = (w_mm as f64 / 25.4, h_mm as f64 / 25.4);
// due to edid's setting non-sensicle values,
// lets be really careful we don't devide by 0 (or non-normal values) when deriving values from size.
// (also the size should be positive, but the u16 arguments already force that.)
if !w_in.is_normal() || !h_in.is_normal() {
return 1.0;
}
let diag = (w_in.powf(2.) * h_in.powf(2.)).sqrt();
let dpi = (w_px as f64 / w_in + h_px as f64 / h_in) / 2.0;
let shorter_res = if w_mm < h_mm { w_px } else { h_px };
match diag {
_diag if diag < 20. || interface == connector::Interface::EmbeddedDisplayPort => {
// likely laptop
scale_from_dpi(dpi, 144, shorter_res)
}
// 16:10 has a height of 19.6, anything lower is most likely an ultrawide
_diag if diag >= 40. && h_in >= 19. => {
// likely TV
match shorter_res {
px if px <= 1200 => 1.0,
_ => 2.0,
}
}
_ => {
// likely desktop
scale_from_dpi(dpi, 120, shorter_res)
}
}
}
pub fn scale_from_dpi(dpi: f64, step_size: u32, shorter_px: u16) -> f64 {
let scale = (dpi / step_size as f64) * 100.0;
// snap to 50%
let scale = ((scale.round() as u32).next_multiple_of(50) as f64) / 100.0;
// max values
let max = match shorter_px {
px if px <= 1200 => 1.0, // 125% is usually a worse experience than 100% atm
// for 1440p and weird variants we let the algorithm take over, but limit the max, this is inspired by what windows does
px if px <= 1600 => 1.5,
_ => 2.0, // never go higher than 200% by default though (Note for the future: Tablets? Phones?)
};
let min = match shorter_px {
px if px >= 2000 => 2.0, // 4k should default to 200%, as we prefer integer scales
_ => 1.0, // never go lower than 100%
};
scale.min(max).max(min)
}
#[cfg(test)]
mod test {
use super::{calculate_scale, connector::Interface};
#[test]
fn test_scale() {
fn scale(interface: Interface, diag_in: f64, w_px: u16, h_px: u16) -> i32 {
let aspect = (w_px as f64) / (h_px as f64);
let diag_mm = diag_in * 25.4;
let h_mm = diag_mm / (aspect.powf(2.0) + 1.0).sqrt();
let w_mm = h_mm * aspect;
(calculate_scale(interface, (w_mm as u32, h_mm as u32), (w_px, h_px)) * 100.0) as i32
}
// Laptops
for &iface in &[Interface::EmbeddedDisplayPort, Interface::LVDS] {
// 14 inch 3:2
assert_eq!(scale(iface, 14.0, 3000, 2000), 200);
// Various sizes 16:9
for &diag_in in &[14.0, 15.6, 17.3] {
assert_eq!(scale(iface, diag_in, 1920, 1080), 100);
assert_eq!(scale(iface, diag_in, 2560, 1440), 150);
assert_eq!(scale(iface, diag_in, 3840, 2160), 200);
}
// Various sizes 16:10
for &diag_in in &[14.0, 16.0] {
assert_eq!(scale(iface, diag_in, 1920, 1200), 100);
assert_eq!(scale(iface, diag_in, 2560, 1600), 150);
assert_eq!(scale(iface, diag_in, 3840, 2400), 200);
}
}
// Desktops
for &iface in &[Interface::DisplayPort, Interface::HDMIA, Interface::HDMIB] {
// 24 inch 16:9
assert_eq!(scale(iface, 24.0, 1920, 1080), 100);
assert_eq!(scale(iface, 24.0, 2560, 1440), 150);
assert_eq!(scale(iface, 24.0, 3840, 2160), 200);
// Larger sizes 16:9
for &diag_in in &[27.0, 32.0, 38.0] {
assert_eq!(scale(iface, diag_in, 1920, 1080), 100);
assert_eq!(scale(iface, diag_in, 2560, 1440), 100);
assert_eq!(scale(iface, diag_in, 3840, 2160), 200);
}
// Smaller sizes 21:9
for &diag_in in &[26.0, 29.0] {
assert_eq!(scale(iface, diag_in, 2560, 1080), 100);
assert_eq!(scale(iface, diag_in, 3440, 1440), 150);
assert_eq!(scale(iface, diag_in, 5120, 2160), 200);
}
// Larger sizes 21:9
for &diag_in in &[34.0, 45.0] {
assert_eq!(scale(iface, diag_in, 2560, 1080), 100);
assert_eq!(scale(iface, diag_in, 3440, 1440), 100);
assert_eq!(scale(iface, diag_in, 5120, 2160), 200);
}
// Various sizes 32:9
for &diag_in in &[45.0, 49.0, 57.0] {
assert_eq!(scale(iface, diag_in, 3840, 1080), 100);
assert_eq!(scale(iface, diag_in, 5120, 1440), 100);
assert_eq!(scale(iface, diag_in, 7680, 2160), 200);
}
}
// TVs
for &iface in &[Interface::DisplayPort, Interface::HDMIA, Interface::HDMIB] {
// Various sizes 16:9
for &diag_in in &[40.0, 42.0, 48.0, 55.0, 65.0, 77.0, 83.0] {
assert_eq!(scale(iface, diag_in, 1920, 1080), 100);
assert_eq!(scale(iface, diag_in, 2560, 1440), 200);
assert_eq!(scale(iface, diag_in, 3840, 2160), 200);
}
}
// Zero sized displays (projectors, invalid EDID)
for &iface in &[Interface::DisplayPort, Interface::HDMIA, Interface::HDMIB] {
assert_eq!(scale(iface, 0.0, 1920, 1080), 100);
assert_eq!(scale(iface, 0.0, 2560, 1440), 100);
assert_eq!(scale(iface, 0.0, 3840, 2160), 100);
}
}
}