kms: working state
This commit is contained in:
parent
825430fdfd
commit
4e238d8848
3 changed files with 783 additions and 6 deletions
329
src/backend/kms/crtc_mapping.rs
Normal file
329
src/backend/kms/crtc_mapping.rs
Normal file
|
|
@ -0,0 +1,329 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use smithay::{
|
||||||
|
reexports::drm::control::{
|
||||||
|
AtomicCommitFlags,
|
||||||
|
Device as ControlDevice,
|
||||||
|
ResourceHandle,
|
||||||
|
atomic::AtomicModeReq,
|
||||||
|
crtc,
|
||||||
|
connector::{
|
||||||
|
self,
|
||||||
|
State as ConnectorState,
|
||||||
|
},
|
||||||
|
dumbbuffer::DumbBuffer,
|
||||||
|
property,
|
||||||
|
Mode,
|
||||||
|
ModeFlags,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub fn display_configuration(device: &mut impl ControlDevice, supports_atomic: bool) -> Result<HashMap<connector::Handle, crtc::Handle>> {
|
||||||
|
let res_handles = device.resource_handles()?;
|
||||||
|
let connectors = res_handles.connectors();
|
||||||
|
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
let mut cleanup = Vec::new();
|
||||||
|
// We expect the previous running drm master (likely the login mananger)
|
||||||
|
// to leave the drm device in a sensible state.
|
||||||
|
// That means, to reduce flickering, we try to keep an established mapping.
|
||||||
|
for conn in connectors
|
||||||
|
.iter()
|
||||||
|
.flat_map(|conn| device.get_connector(*conn).ok())
|
||||||
|
{
|
||||||
|
if let Some(enc) = device.get_connector(conn.handle())?.current_encoder() {
|
||||||
|
if let Some(crtc) = device.get_encoder(enc)?.crtc() {
|
||||||
|
// If is is connected we found a mapping
|
||||||
|
if conn.state() == ConnectorState::Connected {
|
||||||
|
map.insert(conn.handle(), crtc);
|
||||||
|
// If not, the user just unplugged something,
|
||||||
|
// or the drm master did not cleanup?
|
||||||
|
// Well, I guess we cleanup after them.
|
||||||
|
} else {
|
||||||
|
cleanup.push(crtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// But just in case we try to match all remaining connectors.
|
||||||
|
for conn in connectors
|
||||||
|
.iter()
|
||||||
|
.flat_map(|conn| device.get_connector(*conn).ok())
|
||||||
|
.filter(|conn| conn.state() == ConnectorState::Connected)
|
||||||
|
.filter(|conn| !map.contains_key(&conn.handle()))
|
||||||
|
.collect::<Vec<_>>().iter()
|
||||||
|
{
|
||||||
|
'outer: for encoder_info in conn
|
||||||
|
.encoders()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|e| *e)
|
||||||
|
.flat_map(|encoder_handle| device.get_encoder(encoder_handle))
|
||||||
|
{
|
||||||
|
for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) {
|
||||||
|
if !map.values().any(|v| *v == crtc) {
|
||||||
|
map.insert(conn.handle(), crtc);
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And then cleanup
|
||||||
|
if supports_atomic {
|
||||||
|
let mut req = AtomicModeReq::new();
|
||||||
|
let plane_handles = device.plane_handles()?;
|
||||||
|
|
||||||
|
for conn in connectors
|
||||||
|
.iter()
|
||||||
|
.flat_map(|conn| device.get_connector(*conn).ok())
|
||||||
|
.flat_map(|conn| conn.current_encoder())
|
||||||
|
.flat_map(|enc| device.get_encoder(enc).ok())
|
||||||
|
.flat_map(|enc| enc.crtc())
|
||||||
|
.filter(|c| cleanup.contains(&c))
|
||||||
|
{
|
||||||
|
let crtc_id = get_prop(device, conn, "CRTC_ID")?;
|
||||||
|
req.add_property(conn, crtc_id, property::Value::CRTC(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot just shortcut and use the legacy api for all cleanups because of this.
|
||||||
|
// (Technically a device does not need to be atomic for planes to be used, but nobody does this otherwise.)
|
||||||
|
for plane in plane_handles.planes() {
|
||||||
|
let info = device.get_plane(*plane)?;
|
||||||
|
if let Some(crtc) = info.crtc() {
|
||||||
|
if cleanup.contains(&crtc) {
|
||||||
|
let crtc_id = get_prop(device, *plane, "CRTC_ID")?;
|
||||||
|
let fb_id = get_prop(device, *plane, "FB_ID")?;
|
||||||
|
req.add_property(*plane, crtc_id, property::Value::CRTC(None));
|
||||||
|
req.add_property(*plane, fb_id, property::Value::Framebuffer(None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for crtc in cleanup {
|
||||||
|
let mode_id = get_prop(device, crtc, "MODE_ID")?;
|
||||||
|
let active = get_prop(device, crtc, "ACTIVE")?;
|
||||||
|
req.add_property(crtc, active, property::Value::Boolean(false));
|
||||||
|
req.add_property(crtc, mode_id, property::Value::Unknown(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
|
||||||
|
} else {
|
||||||
|
for crtc in cleanup {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let _ = device.set_cursor(crtc, Option::<&DumbBuffer>::None);
|
||||||
|
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
|
||||||
|
let _ = device.set_crtc(crtc, None, (0, 0), &[], None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interface_name(device: &impl ControlDevice, connector: connector::Handle) -> Result<String> {
|
||||||
|
let conn_info = device.get_connector(connector)?;
|
||||||
|
|
||||||
|
let other_short_name;
|
||||||
|
let interface_short_name = match conn_info.interface() {
|
||||||
|
connector::Interface::DVII => "DVI-I",
|
||||||
|
connector::Interface::DVID => "DVI-D",
|
||||||
|
connector::Interface::DVIA => "DVI-A",
|
||||||
|
connector::Interface::SVideo => "S-VIDEO",
|
||||||
|
connector::Interface::DisplayPort => "DP",
|
||||||
|
connector::Interface::HDMIA => "HDMI-A",
|
||||||
|
connector::Interface::HDMIB => "HDMI-B",
|
||||||
|
connector::Interface::EmbeddedDisplayPort => "eDP",
|
||||||
|
other => {
|
||||||
|
other_short_name = format!("{:?}", other);
|
||||||
|
&other_short_name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!("{}-{}", interface_short_name, conn_info.interface_id()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EdidInfo {
|
||||||
|
pub model: String,
|
||||||
|
pub manufacturer: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edid_info(device: &impl ControlDevice, connector: connector::Handle) -> Result<EdidInfo> {
|
||||||
|
use edid_rs::{parse as edid_parse, MonitorDescriptor};
|
||||||
|
|
||||||
|
let edid_prop = get_prop(device, connector, "EDID")?;
|
||||||
|
let edid_info = device.get_property(edid_prop)?;
|
||||||
|
let mut manufacturer = "Unknown".into();
|
||||||
|
let mut model = "Unknown".into();
|
||||||
|
let props = device.get_properties(connector)?;
|
||||||
|
let (ids, vals) = props.as_props_and_values();
|
||||||
|
for (&id, &val) in ids.iter().zip(vals.iter()) {
|
||||||
|
if id == edid_prop {
|
||||||
|
if let property::Value::Blob(edid_blob) =
|
||||||
|
edid_info.value_type().convert_value(val)
|
||||||
|
{
|
||||||
|
let blob = device.get_property_blob(edid_blob)?;
|
||||||
|
let mut reader = std::io::Cursor::new(blob);
|
||||||
|
if let Some(edid) = edid_parse(&mut reader).ok() {
|
||||||
|
manufacturer = {
|
||||||
|
let id = edid.product.manufacturer_id;
|
||||||
|
let code = [id.0, id.1, id.2];
|
||||||
|
get_manufacturer(&code).into()
|
||||||
|
};
|
||||||
|
model = if let Some(MonitorDescriptor::MonitorName(name)) = edid.descriptors.0
|
||||||
|
.iter()
|
||||||
|
.find(|x| matches!(x, MonitorDescriptor::MonitorName(_)))
|
||||||
|
{
|
||||||
|
name.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}", edid.product.product_code)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EdidInfo {
|
||||||
|
model,
|
||||||
|
manufacturer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_prop(device: &impl ControlDevice, handle: impl ResourceHandle, name: &str) -> Result<property::Handle> {
|
||||||
|
let props = device.get_properties(handle)?;
|
||||||
|
let (prop_handles, _) = props.as_props_and_values();
|
||||||
|
for prop in prop_handles {
|
||||||
|
let info = device.get_property(*prop)?;
|
||||||
|
if Some(name) == info.name().to_str().ok() {
|
||||||
|
return Ok(*prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow::bail!("No prop found")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_property_val(device: &impl ControlDevice, handle: impl ResourceHandle, name: &str) -> Result<(property::ValueType, property::RawValue)> {
|
||||||
|
let props = device.get_properties(handle)?;
|
||||||
|
let (prop_handles, values) = props.as_props_and_values();
|
||||||
|
for (&prop, &val) in prop_handles.iter().zip(values.iter()) {
|
||||||
|
let info = device.get_property(prop)?;
|
||||||
|
if Some(name) == info.name().to_str().ok() {
|
||||||
|
let val_type = info.value_type();
|
||||||
|
return Ok((val_type, val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow::bail!("No prop found")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_manufacturer(vendor: &[char; 3]) -> &'static str {
|
||||||
|
match vendor {
|
||||||
|
['A', 'A', 'A'] => "Avolites Ltd",
|
||||||
|
['A', 'C', 'I'] => "Ancor Communications Inc",
|
||||||
|
['A', 'C', 'R'] => "Acer Technologies",
|
||||||
|
['A', 'D', 'A'] => "Addi-Data GmbH",
|
||||||
|
['A', 'P', 'P'] => "Apple Computer Inc",
|
||||||
|
['A', 'S', 'K'] => "Ask A/S",
|
||||||
|
['A', 'V', 'T'] => "Avtek (Electronics) Pty Ltd",
|
||||||
|
['B', 'N', 'O'] => "Bang & Olufsen",
|
||||||
|
['B', 'N', 'Q'] => "BenQ Corporation",
|
||||||
|
['C', 'M', 'N'] => "Chimei Innolux Corporation",
|
||||||
|
['C', 'M', 'O'] => "Chi Mei Optoelectronics corp.",
|
||||||
|
['C', 'R', 'O'] => "Extraordinary Technologies PTY Limited",
|
||||||
|
['D', 'E', 'L'] => "Dell Inc.",
|
||||||
|
['D', 'G', 'C'] => "Data General Corporation",
|
||||||
|
['D', 'O', 'N'] => "DENON, Ltd.",
|
||||||
|
['E', 'N', 'C'] => "Eizo Nanao Corporation",
|
||||||
|
['E', 'P', 'H'] => "Epiphan Systems Inc.",
|
||||||
|
['E', 'X', 'P'] => "Data Export Corporation",
|
||||||
|
['F', 'N', 'I'] => "Funai Electric Co., Ltd.",
|
||||||
|
['F', 'U', 'S'] => "Fujitsu Siemens Computers GmbH",
|
||||||
|
['G', 'S', 'M'] => "Goldstar Company Ltd",
|
||||||
|
['H', 'I', 'Q'] => "Kaohsiung Opto Electronics Americas, Inc.",
|
||||||
|
['H', 'S', 'D'] => "HannStar Display Corp",
|
||||||
|
['H', 'T', 'C'] => "Hitachi Ltd",
|
||||||
|
['H', 'W', 'P'] => "Hewlett Packard",
|
||||||
|
['I', 'N', 'T'] => "Interphase Corporation",
|
||||||
|
['I', 'N', 'X'] => "Communications Supply Corporation (A division of WESCO)",
|
||||||
|
['I', 'T', 'E'] => "Integrated Tech Express Inc",
|
||||||
|
['I', 'V', 'M'] => "Iiyama North America",
|
||||||
|
['L', 'E', 'N'] => "Lenovo Group Limited",
|
||||||
|
['M', 'A', 'X'] => "Rogen Tech Distribution Inc",
|
||||||
|
['M', 'E', 'G'] => "Abeam Tech Ltd",
|
||||||
|
['M', 'E', 'I'] => "Panasonic Industry Company",
|
||||||
|
['M', 'T', 'C'] => "Mars-Tech Corporation",
|
||||||
|
['M', 'T', 'X'] => "Matrox",
|
||||||
|
['N', 'E', 'C'] => "NEC Corporation",
|
||||||
|
['N', 'E', 'X'] => "Nexgen Mediatech Inc.",
|
||||||
|
['O', 'N', 'K'] => "ONKYO Corporation",
|
||||||
|
['O', 'R', 'N'] => "ORION ELECTRIC CO., LTD.",
|
||||||
|
['O', 'T', 'M'] => "Optoma Corporation",
|
||||||
|
['O', 'V', 'R'] => "Oculus VR, Inc.",
|
||||||
|
['P', 'H', 'L'] => "Philips Consumer Electronics Company",
|
||||||
|
['P', 'I', 'O'] => "Pioneer Electronic Corporation",
|
||||||
|
['P', 'N', 'R'] => "Planar Systems, Inc.",
|
||||||
|
['Q', 'D', 'S'] => "Quanta Display Inc.",
|
||||||
|
['R', 'A', 'T'] => "Rent-A-Tech",
|
||||||
|
['R', 'E', 'N'] => "Renesas Technology Corp.",
|
||||||
|
['S', 'A', 'M'] => "Samsung Electric Company",
|
||||||
|
['S', 'A', 'N'] => "Sanyo Electric Co., Ltd.",
|
||||||
|
['S', 'E', 'C'] => "Seiko Epson Corporation",
|
||||||
|
['S', 'H', 'P'] => "Sharp Corporation",
|
||||||
|
['S', 'I', 'I'] => "Silicon Image, Inc.",
|
||||||
|
['S', 'N', 'Y'] => "Sony",
|
||||||
|
['S', 'T', 'D'] => "STD Computer Inc",
|
||||||
|
['S', 'V', 'S'] => "SVSI",
|
||||||
|
['S', 'Y', 'N'] => "Synaptics Inc",
|
||||||
|
['T', 'C', 'L'] => "Technical Concepts Ltd",
|
||||||
|
['T', 'O', 'P'] => "Orion Communications Co., Ltd.",
|
||||||
|
['T', 'S', 'B'] => "Toshiba America Info Systems Inc",
|
||||||
|
['T', 'S', 'T'] => "Transtream Inc",
|
||||||
|
['U', 'N', 'K'] => "Unknown",
|
||||||
|
['V', 'E', 'S'] => "Vestel Elektronik Sanayi ve Ticaret A. S.",
|
||||||
|
['V', 'I', 'T'] => "Visitech AS",
|
||||||
|
['V', 'I', 'Z'] => "VIZIO, Inc",
|
||||||
|
['V', 'S', 'C'] => "ViewSonic Corporation",
|
||||||
|
['Y', 'M', 'H'] => "Yamaha Corporation",
|
||||||
|
_ => "Unknown",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_refresh_rate(mode: Mode) -> u32 {
|
||||||
|
let htotal = mode.hsync().2 as u32;
|
||||||
|
let vtotal = mode.vsync().2 as u32;
|
||||||
|
let mut refresh = (mode.clock() as u64 * 1000000_u64 / htotal as u64 +
|
||||||
|
vtotal as u64 / 2) / vtotal as u64;
|
||||||
|
|
||||||
|
if mode.flags().contains(ModeFlags::INTERLACE) {
|
||||||
|
refresh *= 2;
|
||||||
|
}
|
||||||
|
if mode.flags().contains(ModeFlags::DBLSCAN) {
|
||||||
|
refresh /= 2;
|
||||||
|
}
|
||||||
|
if mode.vscan() > 1 {
|
||||||
|
refresh /= mode.vscan() as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn supports_vrr(dev: &impl ControlDevice, conn: connector::Handle) -> Result<bool> {
|
||||||
|
get_property_val(dev, conn, "vrr_capable")
|
||||||
|
.map(|(val_type, val)| match val_type.convert_value(val) {
|
||||||
|
property::Value::UnsignedRange(res) => res == 1,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_vrr(dev: &impl ControlDevice, crtc: crtc::Handle, conn: connector::Handle, vrr: bool) -> Result<bool> {
|
||||||
|
if supports_vrr(dev, conn)? {
|
||||||
|
dev.set_property(conn, get_prop(dev, crtc, "VRR_ENABLED")?, property::Value::UnsignedRange(if vrr { 1 } else { 0 }).into())
|
||||||
|
.map_err(Into::<anyhow::Error>::into)
|
||||||
|
.and_then(|_| get_property_val(dev, crtc, "VRR_ENABLED"))
|
||||||
|
.map(|(val_type, val)| match val_type.convert_value(val) {
|
||||||
|
property::Value::UnsignedRange(vrr) => vrr == 1,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,428 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::{
|
||||||
use anyhow::Result;
|
state::{BackendData, Common, State},
|
||||||
use smithay::reexports::calloop::EventLoop;
|
utils::GlobalDrop,
|
||||||
|
};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use smithay::{
|
||||||
|
backend::{
|
||||||
|
allocator::{gbm::GbmDevice, Format},
|
||||||
|
drm::{DrmDevice, DrmEvent, GbmBufferedSurface, DrmEventTime},
|
||||||
|
egl::{EGLDevice, EGLDisplay, EGLContext},
|
||||||
|
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||||
|
session::{Session, Signal, auto::AutoSession, AsErrno},
|
||||||
|
udev::{UdevBackend, UdevEvent, primary_gpu},
|
||||||
|
renderer::{Bind, gles2::Gles2Renderer},
|
||||||
|
},
|
||||||
|
reexports::{
|
||||||
|
drm::{
|
||||||
|
control::{
|
||||||
|
Device as ControlDevice,
|
||||||
|
connector,
|
||||||
|
crtc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calloop::{EventLoop, Dispatcher, RegistrationToken, LoopHandle, timer::{Timer, TimerHandle}},
|
||||||
|
input::Libinput,
|
||||||
|
nix::{
|
||||||
|
fcntl::OFlag,
|
||||||
|
sys::stat::dev_t,
|
||||||
|
},
|
||||||
|
wayland_server::{
|
||||||
|
Display,
|
||||||
|
protocol::wl_output,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wayland::output::{Output, Mode as OutputMode, PhysicalProperties},
|
||||||
|
utils::signaling::{Signaler, Linkable},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
path::PathBuf,
|
||||||
|
rc::Rc,
|
||||||
|
time::{Duration, Instant, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct KmsState {}
|
mod crtc_mapping;
|
||||||
|
mod session_fd;
|
||||||
|
use session_fd::*;
|
||||||
|
|
||||||
|
pub struct KmsState {
|
||||||
|
devices: HashMap<dev_t, Device>,
|
||||||
|
session: AutoSession,
|
||||||
|
signaler: Signaler<Signal>,
|
||||||
|
tokens: Vec<RegistrationToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Device {
|
||||||
|
renderer: Gles2Renderer,
|
||||||
|
surfaces: HashMap<crtc::Handle, Surface>,
|
||||||
|
allocator: Rc<RefCell<GbmDevice<SessionFd>>>,
|
||||||
|
drm: Dispatcher<'static, DrmDevice<SessionFd>, State>,
|
||||||
|
formats: HashSet<Format>,
|
||||||
|
supports_atomic: bool,
|
||||||
|
event_token: Option<RegistrationToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Surface {
|
||||||
|
surface: GbmBufferedSurface<Rc<RefCell<GbmDevice<SessionFd>>>, SessionFd>,
|
||||||
|
output: Output,
|
||||||
|
_global: GlobalDrop<wl_output::WlOutput>,
|
||||||
|
last_submit: Option<DrmEventTime>,
|
||||||
|
refresh_rate: u32,
|
||||||
|
vrr: bool,
|
||||||
|
pending: bool,
|
||||||
|
render_timer: TimerHandle<(dev_t, crtc::Handle)>,
|
||||||
|
render_timer_token: Option<RegistrationToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub struct DevId(pub dev_t);
|
||||||
|
|
||||||
pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Result<()> {
|
pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Result<()> {
|
||||||
unimplemented!()
|
let (session, notifier) = AutoSession::new(None).context("Failed to acquire session")?;
|
||||||
|
let signaler = notifier.signaler();
|
||||||
|
|
||||||
|
let udev_backend = UdevBackend::new(session.seat(), None)?;
|
||||||
|
let mut libinput_context = Libinput::new_with_udev::<LibinputSessionInterface<AutoSession>>(session.clone().into());
|
||||||
|
libinput_context.udev_assign_seat(&session.seat()).map_err(|_| anyhow::anyhow!("Failed to assign seat to libinput"))?;
|
||||||
|
let mut libinput_backend = LibinputInputBackend::new(libinput_context, None);
|
||||||
|
libinput_backend.link(signaler.clone());
|
||||||
|
|
||||||
|
let libinput_event_source = event_loop
|
||||||
|
.handle()
|
||||||
|
.insert_source(libinput_backend, move |event, _, state| {
|
||||||
|
state.common.process_input_event(event);
|
||||||
|
for output in state.common.spaces.outputs() {
|
||||||
|
state.backend.kms().schedule_render(output);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|err| err.error)
|
||||||
|
.context("Failed to initialize libinput event source")?;
|
||||||
|
let session_event_source = event_loop
|
||||||
|
.handle()
|
||||||
|
.insert_source(notifier, |(), &mut (), _state| {})
|
||||||
|
.map_err(|err| err.error)
|
||||||
|
.context("Failed to initialize session event source")?;
|
||||||
|
|
||||||
|
state.backend = BackendData::Kms(KmsState {
|
||||||
|
tokens: vec![
|
||||||
|
libinput_event_source,
|
||||||
|
session_event_source,
|
||||||
|
],
|
||||||
|
session,
|
||||||
|
signaler,
|
||||||
|
devices: HashMap::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (dev, path) in udev_backend.device_list() {
|
||||||
|
state.device_added(dev, path.into())
|
||||||
|
.with_context(|| format!("Failed to add drm device: {}", path.display()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let udev_event_source = event_loop
|
||||||
|
.handle()
|
||||||
|
.insert_source(udev_backend, move |event, _, state| match match event {
|
||||||
|
UdevEvent::Added { device_id, path } => state.device_added(device_id, path)
|
||||||
|
.with_context(|| format!("Failed to add drm device: {}", device_id)),
|
||||||
|
UdevEvent::Changed { device_id } => state.device_changed(device_id)
|
||||||
|
.with_context(|| format!("Failed to update drm device: {}", device_id)),
|
||||||
|
UdevEvent::Removed { device_id } => state.device_removed(device_id)
|
||||||
|
.with_context(|| format!("Failed to remove drm device: {}", device_id)),
|
||||||
|
} {
|
||||||
|
Ok(()) => { slog_scope::debug!("Successfully handled udev event") },
|
||||||
|
Err(err) => { slog_scope::error!("Error while handling udev event: {}", err) },
|
||||||
|
}).unwrap();
|
||||||
|
state.backend.kms().tokens.push(udev_event_source);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn device_added(&mut self, dev: dev_t, path: PathBuf) -> Result<()> {
|
||||||
|
let fd = SessionFd::new(self.backend.kms().session.open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK)
|
||||||
|
.with_context(|| format!("Failed to optain file descriptor for drm device: {}", path.display()))?);
|
||||||
|
let mut drm = DrmDevice::new(fd.clone(), false, None)
|
||||||
|
.with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?;
|
||||||
|
let supports_atomic = drm.is_atomic();
|
||||||
|
|
||||||
|
let egl_device = EGLDevice::enumerate().context("Failed to enumerate EGLDevices")?
|
||||||
|
// TODO: this check compares the primary node path.
|
||||||
|
// On split display controller setups however this *may* return the render node
|
||||||
|
// *or* if `EGL_EXT_device_drm_render_node` is supported nothing and we need to
|
||||||
|
// query `EGL_DRM_RENDER_NODE_FILE_EXT` instead for comparision.
|
||||||
|
.find(|dev| dev.drm_device_path().map(|p| p == path).unwrap_or(false))
|
||||||
|
.with_context(|| format!("Unable to find matching egl device for {}", path.display()))?;
|
||||||
|
let egl_display = EGLDisplay::new(&egl_device, None)
|
||||||
|
.with_context(|| format!("Failed to open EGLDisplay for device {:?}:{}", egl_device, path.display()))?;
|
||||||
|
let egl_context = EGLContext::new(&egl_display, None)
|
||||||
|
.with_context(|| format!("Failed to create EGLContext for device {:?}:{}", egl_device, path.display()))?;
|
||||||
|
let formats = egl_context.dmabuf_render_formats().clone();
|
||||||
|
let renderer = unsafe { Gles2Renderer::new(egl_context, None) }
|
||||||
|
.with_context(|| format!("Failed to create OpenGLES renderer for device {:?}:{}", egl_device, path.display()))?;
|
||||||
|
|
||||||
|
let gbm = GbmDevice::new(fd).with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?;
|
||||||
|
drm.link(self.backend.kms().signaler.clone());
|
||||||
|
let dispatcher = Dispatcher::new(drm, move |event, metadata, state: &mut State| {
|
||||||
|
match event {
|
||||||
|
DrmEvent::VBlank(crtc) => {
|
||||||
|
if let Some(device) = state.backend.kms().devices.get_mut(&dev) {
|
||||||
|
if let Some(surface) = device.surfaces.get_mut(&crtc) {
|
||||||
|
match surface.surface.frame_submitted() {
|
||||||
|
Ok(_) => {
|
||||||
|
surface.last_submit = metadata.take().map(|data| data.time);
|
||||||
|
surface.pending = false;
|
||||||
|
state.common.spaces.active_space_mut(&surface.output)
|
||||||
|
.send_frames(true, state.common.start_time.elapsed().as_millis() as u32);
|
||||||
|
},
|
||||||
|
Err(err) => slog_scope::warn!("Failed to submit frame: {}", err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DrmEvent::Error(err) => {
|
||||||
|
slog_scope::warn!("Failed to read events of device {:?}: {}", dev, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let token = self.common.event_loop_handle.register_dispatcher(dispatcher.clone())
|
||||||
|
.with_context(|| format!("Failed to add drm device to event loop: {}", dev))?;
|
||||||
|
|
||||||
|
let mut device = Device {
|
||||||
|
renderer,
|
||||||
|
surfaces: HashMap::new(),
|
||||||
|
allocator: Rc::new(RefCell::new(gbm)),
|
||||||
|
drm: dispatcher,
|
||||||
|
formats,
|
||||||
|
supports_atomic,
|
||||||
|
event_token: Some(token),
|
||||||
|
};
|
||||||
|
|
||||||
|
let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices
|
||||||
|
for (crtc, conn) in outputs {
|
||||||
|
match device.setup_surface(crtc, conn, self.backend.kms().signaler.clone(), &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle) {
|
||||||
|
Ok(output) => self.common.spaces.map_output(&output),
|
||||||
|
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backend.kms().devices.insert(dev, device);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_changed(&mut self, dev: dev_t) -> Result<()> {
|
||||||
|
let signaler = self.backend.kms().signaler.clone();
|
||||||
|
if let Some(device) = self.backend.kms().devices.get_mut(&dev) {
|
||||||
|
let changes = device.enumerate_surfaces()?;
|
||||||
|
for (crtc, _) in changes.removed {
|
||||||
|
if let Some(surface) = device.surfaces.get_mut(&crtc) {
|
||||||
|
if let Some(token) = surface.render_timer_token.take() {
|
||||||
|
self.common.event_loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
self.common.spaces.unmap_output(&surface.output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (crtc, conn) in changes.added {
|
||||||
|
match device.setup_surface(crtc, conn, signaler.clone(), &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle) {
|
||||||
|
Ok(output) => self.common.spaces.map_output(&output),
|
||||||
|
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_removed(&mut self, dev: dev_t) -> Result<()> {
|
||||||
|
if let Some(device) = self.backend.kms().devices.get_mut(&dev) {
|
||||||
|
for surface in device.surfaces.values_mut() {
|
||||||
|
if let Some(token) = surface.render_timer_token.take() {
|
||||||
|
self.common.event_loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
self.common.spaces.unmap_output(&surface.output);
|
||||||
|
}
|
||||||
|
if let Some(token) = device.event_token.take() {
|
||||||
|
self.common.event_loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OutputChanges {
|
||||||
|
pub added: Vec<(crtc::Handle, connector::Handle)>,
|
||||||
|
pub removed: Vec<(crtc::Handle, connector::Handle)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn enumerate_surfaces(&mut self) -> Result<OutputChanges> {
|
||||||
|
let drm = &mut *self.drm.as_source_mut();
|
||||||
|
|
||||||
|
// enumerate our outputs
|
||||||
|
let config = crtc_mapping::display_configuration(drm, self.supports_atomic)?;
|
||||||
|
|
||||||
|
let surfaces = self.surfaces.iter()
|
||||||
|
.map(|(c, s)| (*c, s.surface.current_connectors().into_iter().next().unwrap()))
|
||||||
|
.collect::<HashMap<crtc::Handle, connector::Handle>>();
|
||||||
|
|
||||||
|
let added = config.iter()
|
||||||
|
.filter(|(conn, crtc)| surfaces.get(&crtc).map(|c| c != *conn).unwrap_or(true))
|
||||||
|
.map(|(conn, crtc)| (crtc, conn))
|
||||||
|
.map(|(crtc, conn)| (*crtc, *conn))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let removed = surfaces.iter()
|
||||||
|
.filter(|(crtc, conn)| config.get(&conn).map(|c| c != *crtc).unwrap_or(true))
|
||||||
|
.map(|(crtc, conn)| (*crtc, *conn))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(OutputChanges {
|
||||||
|
added,
|
||||||
|
removed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_surface(
|
||||||
|
&mut self,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
conn: connector::Handle,
|
||||||
|
signaler: Signaler<Signal>,
|
||||||
|
display: &mut Display,
|
||||||
|
loop_handle: &mut LoopHandle<'static, State>,
|
||||||
|
) -> Result<Output> {
|
||||||
|
let drm = &mut *self.drm.as_source_mut();
|
||||||
|
let crtc_info = drm.get_crtc(crtc)?;
|
||||||
|
let conn_info = drm.get_connector(conn)?;
|
||||||
|
let vrr = crtc_mapping::set_vrr(drm, crtc, conn, true)?;
|
||||||
|
let interface = crtc_mapping::interface_name(drm, conn)?;
|
||||||
|
let edid_info = crtc_mapping::edid_info(drm, conn)?;
|
||||||
|
let mode = crtc_info.mode().unwrap_or(conn_info.modes()[0]);
|
||||||
|
let mut surface = drm.create_surface(crtc, mode, &[conn])?;
|
||||||
|
surface.link(signaler);
|
||||||
|
|
||||||
|
let target = GbmBufferedSurface::new(surface, self.allocator.clone(), self.formats.clone(), None)
|
||||||
|
.with_context(|| format!("Failed to initialize Gbm surface for {}", interface))?;
|
||||||
|
|
||||||
|
let output_mode = OutputMode {
|
||||||
|
size: (mode.size().0 as i32, mode.size().1 as i32).into(),
|
||||||
|
refresh: (mode.vrefresh() * 1000) as i32,
|
||||||
|
};
|
||||||
|
let (phys_w, phys_h) = conn_info.size().unwrap_or((0, 0));
|
||||||
|
let (output, output_global) = Output::new(
|
||||||
|
display,
|
||||||
|
interface,
|
||||||
|
PhysicalProperties {
|
||||||
|
size: (phys_w as i32, phys_h as i32).into(),
|
||||||
|
// TODO: We need to read that from the connector properties
|
||||||
|
subpixel: wl_output::Subpixel::Unknown,
|
||||||
|
make: edid_info.manufacturer,
|
||||||
|
model: edid_info.model,
|
||||||
|
},
|
||||||
|
None
|
||||||
|
);
|
||||||
|
output.set_preferred(output_mode.clone());
|
||||||
|
output.change_current_state(
|
||||||
|
Some(output_mode),
|
||||||
|
None, // TODO: Readout property for monitor rotation
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let timer = Timer::new()?;
|
||||||
|
let timer_handle = timer.handle();
|
||||||
|
// render timer
|
||||||
|
let timer_token = loop_handle
|
||||||
|
.insert_source(timer, |(dev_id, crtc), _, state| {
|
||||||
|
if let Some(device) = state.backend.kms().devices.get_mut(&dev_id) {
|
||||||
|
let renderer = &mut device.renderer;
|
||||||
|
if let Some(surface) = device.surfaces.get_mut(&crtc) {
|
||||||
|
if let Err(err) = surface.render_output(renderer, &mut state.common) {
|
||||||
|
slog_scope::error!("Error rendering: {}", err);
|
||||||
|
// TODO re-schedule?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
timer_handle.add_timeout(Duration::ZERO, (drm.device_id(), crtc));
|
||||||
|
|
||||||
|
let data = Surface {
|
||||||
|
output: output.clone(),
|
||||||
|
_global: output_global.into(),
|
||||||
|
surface: target,
|
||||||
|
vrr,
|
||||||
|
refresh_rate: crtc_mapping::calculate_refresh_rate(mode),
|
||||||
|
last_submit: None,
|
||||||
|
pending: true,
|
||||||
|
render_timer: timer_handle,
|
||||||
|
render_timer_token: Some(timer_token),
|
||||||
|
};
|
||||||
|
self.surfaces.insert(crtc, data);
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Surface {
|
||||||
|
pub fn render_output(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut Gles2Renderer,
|
||||||
|
state: &mut Common,
|
||||||
|
) -> Result<()> {
|
||||||
|
let custom_elements = Vec::new();
|
||||||
|
|
||||||
|
let space = state.spaces.active_space_mut(&self.output);
|
||||||
|
let (buffer, age) = self
|
||||||
|
.surface
|
||||||
|
.next_buffer()
|
||||||
|
.with_context(|| "Failed to allocate buffer")?;
|
||||||
|
renderer
|
||||||
|
.bind(buffer)
|
||||||
|
.with_context(|| "Failed to bind buffer")?;
|
||||||
|
match space.render_output(
|
||||||
|
renderer,
|
||||||
|
&self.output,
|
||||||
|
age as usize,
|
||||||
|
[0.153, 0.161, 0.165, 1.0],
|
||||||
|
&*custom_elements,
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.surface
|
||||||
|
.queue_buffer()
|
||||||
|
.with_context(|| "Failed to submit buffer for display")?;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
self.surface.reset_buffers();
|
||||||
|
anyhow::bail!("Rendering failed: {}", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KmsState {
|
impl KmsState {
|
||||||
pub fn schedule_render(&mut self, output: &Output) {
|
pub fn schedule_render(&mut self, output: &Output) {
|
||||||
unimplemented!();
|
if let Some((device, surface)) = self.devices.values_mut()
|
||||||
|
.flat_map(|d| {
|
||||||
|
let dev_id = d.drm.as_source_ref().device_id();
|
||||||
|
d.surfaces.values_mut().map(move |s| (dev_id, s))
|
||||||
|
})
|
||||||
|
.find(|(_, s)| s.output == *output)
|
||||||
|
{
|
||||||
|
if !surface.pending {
|
||||||
|
surface.pending = true;
|
||||||
|
let duration = match surface.last_submit {
|
||||||
|
Some(DrmEventTime::Monotonic(instant)) => Instant::now().duration_since(instant),
|
||||||
|
Some(DrmEventTime::Realtime(time)) => SystemTime::now().duration_since(time).unwrap_or(Duration::new(100, 0)),
|
||||||
|
None => Duration::new(100, 0), // Just do an insane amount
|
||||||
|
};
|
||||||
|
let data = (device, surface.surface.crtc());
|
||||||
|
if surface.vrr || 1.0 / (surface.refresh_rate as f64) < duration.as_secs_f64() / 1000.0 {
|
||||||
|
surface.render_timer.add_timeout(Duration::ZERO, data);
|
||||||
|
} else {
|
||||||
|
surface.render_timer.add_timeout(duration.saturating_sub(Duration::from_millis(5)), data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
37
src/backend/kms/session_fd.rs
Normal file
37
src/backend/kms/session_fd.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
rc::Rc,
|
||||||
|
os::unix::io::{RawFd, AsRawFd},
|
||||||
|
};
|
||||||
|
use smithay::reexports::nix::unistd::close;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SessionFd(Rc<DropFd>);
|
||||||
|
|
||||||
|
struct DropFd(RawFd);
|
||||||
|
|
||||||
|
impl SessionFd {
|
||||||
|
pub fn new(fd: RawFd) -> SessionFd {
|
||||||
|
SessionFd(Rc::new(DropFd(fd)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SessionFd {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Session-provided File Descriptor [{}]", self.0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for SessionFd {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DropFd {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Err(err) = close(self.0) {
|
||||||
|
slog_scope::warn!("Failed to close file descriptor {}", self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue