From 7531c8f791a37e8381cab956105c844a6147765d Mon Sep 17 00:00:00 2001 From: kossLAN Date: Fri, 29 May 2026 21:27:55 -0400 Subject: [PATCH] metal: split video discovery --- src/backends/metal/video.rs | 659 +------------------------ src/backends/metal/video/discovery.rs | 660 ++++++++++++++++++++++++++ 2 files changed, 677 insertions(+), 642 deletions(-) create mode 100644 src/backends/metal/video/discovery.rs diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 668c1460..efd447b7 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,4 +1,5 @@ mod copy_device; +mod discovery; mod hardware_cursor; mod lease; mod model; @@ -18,19 +19,22 @@ pub use { properties::{DefaultProperty, TypedProperty}, }; -use properties::{ - DefaultValue, collect_properties, collect_untyped_properties, create_default_properties, +use { + discovery::{ + create_connector, create_connector_display_data, create_crtc, create_encoder, create_plane, + get_connectors, + }, + properties::collect_untyped_properties, }; use { crate::{ - async_engine::Phase, backend::{ BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLessee, BackendEotfs, BackendEvent, BackendGammaLut, BackendGammaLutElement, - BackendLuminance, CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, + CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, - Mode, MonitorInfo, OutputId, + MonitorInfo, transaction::{ BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, @@ -38,42 +42,33 @@ use { }, backends::metal::{ MetalBackend, MetalError, - present::{ - DEFAULT_POST_COMMIT_MARGIN, DEFAULT_PRE_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA, - }, - transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState, MetalDeviceTransaction}, + present::{DEFAULT_POST_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA}, + transaction::MetalDeviceTransaction, }, - cmm::cmm_primaries::Primaries, drm_feedback::DrmFeedback, - edid::{CtaDataBlock, Descriptor, EdidExtension}, format::XRGB8888, gfx_api::GfxApi, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY}, tree::OutputNode, udev::UdevDevice, utils::{ - binary_search_map::BinarySearchMap, bitflags::BitflagsExt, cell_ext::CellExt, - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - geometric_decay::GeometricDecay, numcell::NumCell, ordered_float::F64, - oserror::OsError, + cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, oserror::OsError, }, video::{ - INVALID_MODIFIER, drm::{ - ConnectorStatus, ConnectorType, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources, - DrmConnector, DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFb, DrmMaster, - DrmObject, DrmPlane, DrmProperty, DrmPropertyType, DrmVersion, - HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, hdr_output_metadata, + ConnectorStatus, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources, DrmConnector, + DrmCrtc, DrmError, DrmEvent, DrmFb, DrmMaster, DrmObject, DrmProperty, + DrmVersion, drm_mode_modeinfo, hdr_output_metadata, }, gbm::GbmDevice, }, }, ahash::{AHashMap, AHashSet}, bstr::ByteSlice, - indexmap::indexset, isnt::std_1::collections::IsntHashMapExt, std::{ - cell::{Cell, RefCell}, + cell::Cell, collections::hash_map::Entry, mem, ops::DerefMut, @@ -555,626 +550,6 @@ impl Connector for MetalConnector { } } -fn get_connectors( - backend: &Rc, - dev: &Rc, - ids: &[DrmConnector], -) -> Result< - ( - CopyHashMap>, - CopyHashMap, - ), - DrmError, -> { - let connectors = CopyHashMap::new(); - let futures = CopyHashMap::new(); - for connector in ids { - match create_connector(backend, *connector, dev) { - Ok((con, fut)) => { - let id = con.id; - connectors.set(id, con); - futures.set(id, fut); - } - Err(e) => return Err(DrmError::CreateConnector(Box::new(e))), - } - } - Ok((connectors, futures)) -} - - -fn create_connector( - backend: &Rc, - connector: DrmConnector, - dev: &Rc, -) -> Result<(Rc, ConnectorFutures), DrmError> { - let display = create_connector_display_data(connector, dev)?; - log::info!( - "Creating connector {} for device {}", - display.connector_id, - dev.devnode.as_bytes().as_bstr(), - ); - let slf = Rc::new(MetalConnector { - id: connector, - kernel_id: Cell::new(display.connector_id), - master: dev.master.clone(), - state: backend.state.clone(), - dev: dev.clone(), - backend: backend.clone(), - connector_id: backend.state.connector_ids.next(), - buffers: Default::default(), - color_description: CloneCell::new(backend.state.color_manager.srgb_gamma22().clone()), - lease: Cell::new(None), - buffers_idle: Cell::new(true), - crtc_idle: Cell::new(true), - has_damage: NumCell::new(1), - primary_plane: Default::default(), - cursor_plane: Default::default(), - crtc: Default::default(), - on_change: Default::default(), - present_trigger: Default::default(), - cursor_x: Cell::new(0), - cursor_y: Cell::new(0), - cursor_enabled: Cell::new(false), - cursor_buffers: Default::default(), - display: RefCell::new(display), - frontend_state: Cell::new(FrontState::Removed), - cursor_changed: Cell::new(false), - cursor_damage: Cell::new(false), - cursor_swap_buffer: Cell::new(false), - cursor_sync: Default::default(), - drm_feedback: Default::default(), - scanout_buffers: Default::default(), - active_framebuffer: Default::default(), - next_framebuffer: Default::default(), - direct_scanout_active: Cell::new(false), - next_vblank_nsec: Cell::new(0), - version: Default::default(), - expected_sequence: Default::default(), - pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), - pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), - post_commit_margin_decay: GeometricDecay::new(0.1, dev.min_post_commit_margin.get()), - post_commit_margin: Cell::new(dev.min_post_commit_margin.get()), - vblank_miss_sec: Cell::new(0), - vblank_miss_this_sec: Default::default(), - presentation_is_sync: Cell::new(false), - presentation_is_zero_copy: Cell::new(false), - }); - let futures = ConnectorFutures { - _present: backend.state.eng.spawn2( - "present loop", - Phase::Present, - slf.clone().present_loop(), - ), - }; - Ok((slf, futures)) -} - -fn create_connector_display_data( - connector: DrmConnector, - dev: &Rc, -) -> Result { - let info = dev.master.get_connector_info(connector, true)?; - let mut crtcs = BinarySearchMap::new(); - for encoder in info.encoders { - if let Some(encoder) = dev.encoders.get(&encoder) { - for (_, crtc) in &encoder.crtcs { - crtcs.insert(crtc.id, crtc.clone()); - } - } - } - let props = collect_properties(&dev.master, connector)?; - let connection = ConnectorStatus::from_drm(info.connection); - let mut name = String::new(); - let mut manufacturer = String::new(); - let mut serial_number = String::new(); - let mut vrr_refresh_max_nsec = u64::MAX; - let connector_id = ConnectorKernelId { - ty: ConnectorType::from_drm(info.connector_type), - idx: info.connector_type_id, - }; - let mut supports_bt2020 = false; - let mut supports_pq = false; - let mut luminance = None; - let mut primaries = Primaries::SRGB; - 'fetch_edid: { - if connection != ConnectorStatus::Connected { - break 'fetch_edid; - } - let edid = match props.get("EDID") { - Ok(e) => e, - _ => { - log::warn!( - "Connector {} is connected but has no EDID blob", - connector_id, - ); - break 'fetch_edid; - } - }; - let blob = match dev.master.getblob_vec::(DrmBlob(edid.value as _)) { - Ok(b) => b, - Err(e) => { - log::error!( - "Could not fetch edid property of connector {}: {}", - connector_id, - ErrorFmt(e) - ); - break 'fetch_edid; - } - }; - let edid = match crate::edid::parse(&blob) { - Ok(e) => e, - Err(e) => { - log::error!( - "Could not parse edid property of connector {}: {}", - connector_id, - ErrorFmt(e) - ); - break 'fetch_edid; - } - }; - manufacturer = edid.base_block.id_manufacturer_name.to_string(); - for descriptor in edid.base_block.descriptors.iter().flatten() { - match descriptor { - Descriptor::DisplayProductSerialNumber(s) => { - serial_number.clone_from(s); - } - Descriptor::DisplayProductName(s) => { - name.clone_from(s); - } - _ => {} - } - } - if name.is_empty() { - log::warn!( - "The display attached to connector {} does not have a product name descriptor", - connector_id, - ); - } - if serial_number.is_empty() { - log::warn!( - "The display attached to connector {} does not have a serial number descriptor", - connector_id, - ); - serial_number = edid.base_block.id_serial_number.to_string(); - } - let min_vrr_hz = 'fetch_min_hz: { - for ext in &edid.extension_blocks { - if let EdidExtension::CtaV3(cta) = ext { - for data_block in &cta.data_blocks { - if let CtaDataBlock::VendorAmd(amd) = data_block { - break 'fetch_min_hz amd.minimum_refresh_hz as u64; - } - } - } - } - for desc in &edid.base_block.descriptors { - if let Some(desc) = desc - && let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc - { - break 'fetch_min_hz timings.vertical_field_rate_min as u64; - } - } - 0 - }; - if min_vrr_hz > 0 { - vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz; - } - let cc = &edid.base_block.chromaticity_coordinates; - let map = |c: u16| F64(c as f64 / 1024.0); - primaries = Primaries { - r: (map(cc.red_x), map(cc.red_y)), - g: (map(cc.green_x), map(cc.green_y)), - b: (map(cc.blue_x), map(cc.blue_y)), - wp: (map(cc.white_x), map(cc.white_y)), - }; - for ext in &edid.extension_blocks { - if let EdidExtension::CtaV3(cta) = ext { - for data_block in &cta.data_blocks { - match data_block { - CtaDataBlock::Colorimetry(c) => { - if c.bt2020_rgb { - supports_bt2020 = true; - } - } - CtaDataBlock::StaticHdrMetadata(h) => { - if h.smpte_st_2084 { - supports_pq = true; - } - if let Some(max) = h.max_luminance { - luminance = Some(BackendLuminance { - min: h.min_luminance.unwrap_or(0.0), - max, - max_fall: h.max_luminance.unwrap_or(max), - }); - } - } - _ => {} - } - } - } - } - } - let output_id = OutputId::new(connector_id.to_string(), manufacturer, name, serial_number); - let first_mode = info - .modes - .first() - .cloned() - .map(|m| m.to_backend()) - .unwrap_or_default(); - let persistent = match dev.backend.persistent_display_data.get(&output_id) { - Some(ds) => { - if connection != ConnectorStatus::Disconnected { - log::info!("Reusing desired state for {:?}", output_id); - } - ds - } - None => { - let ds = Rc::new(PersistentDisplayData { - state: RefCell::new(BackendConnectorState { - serial: dev.backend.state.backend_connector_state_serials.next(), - enabled: true, - active: true, - mode: first_mode, - non_desktop_override: None, - vrr: false, - tearing: false, - format: XRGB8888, - color_space: Default::default(), - eotf: Default::default(), - gamma_lut: Default::default(), - }), - }); - dev.backend - .persistent_display_data - .set(output_id.clone(), ds.clone()); - ds - } - }; - let mut desired_state = persistent.state.borrow_mut(); - if desired_state.mode == Mode::default() { - desired_state.mode = first_mode; - } else if info - .modes - .iter() - .all(|m| m.to_backend() != desired_state.mode) - { - log::warn!("Discarding previously desired mode"); - desired_state.mode = first_mode; - } - let non_desktop = props.get("non-desktop")?.value != 0; - let vrr_capable = match props.get("vrr_capable") { - Ok(c) => c.value == 1, - Err(_) => false, - }; - if !vrr_capable && desired_state.vrr { - log::warn!("Connector has lost VRR capability"); - desired_state.vrr = false; - } - { - let viable = match desired_state.eotf { - BackendEotfs::Default => true, - BackendEotfs::Pq => supports_pq, - }; - if !viable { - log::warn!("Discarding previously desired EOTF"); - desired_state.eotf = BackendEotfs::Default; - } - } - { - let viable = match desired_state.color_space { - BackendColorSpace::Default => true, - BackendColorSpace::Bt2020 => supports_bt2020, - }; - if !viable { - log::warn!("Discarding previously desired color space"); - desired_state.color_space = BackendColorSpace::Default; - } - } - drop(desired_state); - let default_properties = create_default_properties( - &props, - &[ - ("Broadcast RGB", DefaultValue::Enum("Automatic")), - ("HDR_SOURCE_METADATA", DefaultValue::Fixed(0)), - ("Output format", DefaultValue::Enum("Default")), - ("WRITEBACK_FB_ID", DefaultValue::Fixed(0)), - ("WRITEBACK_OUT_FENCE_PTR", DefaultValue::Fixed(0)), - ("content type", DefaultValue::Enum("No Data")), - ("dither", DefaultValue::Enum("off")), - ("max bpc", DefaultValue::RangeMax), - ], - ); - let hdr_metadata_prop = props - .get("HDR_OUTPUT_METADATA") - .map(|p| p.map(|v| DrmBlob(v as _))) - .ok(); - let mut hdr_metadata = None; - let mut hdr_metadata_blob_id = DrmBlob::NONE; - if let Some(p) = &hdr_metadata_prop { - hdr_metadata_blob_id = p.value; - hdr_metadata = Some(hdr_output_metadata::from_eotf( - HDMI_EOTF_TRADITIONAL_GAMMA_SDR, - )); - if p.value.is_some() { - match dev.master.getblob::(p.value) { - Ok(m) => hdr_metadata = Some(m), - _ => { - log::debug!("Could not retrieve hdr output metadata"); - } - } - } - } - let colorspace_prop = props.get("Colorspace").ok(); - let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); - let drm_state = DrmConnectorState { - crtc_id: crtc_id.value, - color_space: colorspace_prop.map(|p| p.value), - hdr_metadata, - hdr_metadata_blob_id, - hdr_metadata_blob: None, - locked: true, - fb: DrmFb::NONE, - fb_idx: 0, - cursor_fb: DrmFb::NONE, - cursor_fb_idx: 0, - cursor_x: 0, - cursor_y: 0, - out_fd: None, - src_w: 0, - src_h: 0, - crtc_x: 0, - crtc_y: 0, - crtc_w: 0, - crtc_h: 0, - }; - Ok(ConnectorDisplayData { - crtc_id: props.get("CRTC_ID")?.id, - crtcs, - first_mode, - modes: info.modes, - persistent, - refresh: 0, - non_desktop, - non_desktop_effective: non_desktop, - vrr_capable, - _vrr_refresh_max_nsec: vrr_refresh_max_nsec, - default_properties, - untyped_properties: props.to_untyped(), - connection, - mm_width: info.mm_width, - mm_height: info.mm_height, - _subpixel: info.subpixel, - supports_bt2020, - supports_pq, - primaries, - luminance, - connector_id, - output_id, - colorspace: colorspace_prop.map(|p| p.id), - hdr_metadata: hdr_metadata_prop.map(|p| p.id), - drm_state, - }) -} - -fn create_encoder( - encoder: DrmEncoder, - master: &Rc, - crtcs: &AHashMap>, -) -> Result { - let info = master.get_encoder_info(encoder)?; - let mut possible = AHashMap::new(); - for crtc in crtcs.values() { - if info.possible_crtcs.contains(1 << crtc.idx) { - possible.insert(crtc.id, crtc.clone()); - } - } - Ok(MetalEncoder { - id: encoder, - crtcs: possible, - }) -} - -fn create_crtc( - crtc: DrmCrtc, - idx: usize, - master: &Rc, - planes: &AHashMap>, -) -> Result { - let mask = 1 << idx; - let mut possible_planes = BinarySearchMap::new(); - for plane in planes.values() { - if plane.possible_crtcs.contains(mask) { - possible_planes.insert(plane.id, plane.clone()); - } - } - let props = collect_properties(master, crtc)?; - let default_properties = create_default_properties( - &props, - &[ - ("AMD_CRTC_REGAMMA_TF", DefaultValue::Enum("Default")), - ("CTM", DefaultValue::Fixed(0)), - ("DEGAMMA_LUT", DefaultValue::Fixed(0)), - ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), - ], - ); - let active = props.get("ACTIVE")?.map(|v| v == 1); - let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); - let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); - let out_fence_ptr = props.get("OUT_FENCE_PTR")?; - let gamma_lut = props - .get("GAMMA_LUT") - .ok() - .map(|v| v.map(|v| DrmBlob(v as u32))); - let mut gamma_lut_size = None; - if gamma_lut.is_some() { - gamma_lut_size = props.get("GAMMA_LUT_SIZE").ok().map(|v| v.value as u32); - } - let mut mode = None; - if mode_id.value.is_some() { - match master.getblob::(mode_id.value) { - Ok(m) => mode = Some(m.into()), - _ => { - log::debug!("Could not retrieve current mode of connector"); - } - } - } - let state = DrmCrtcState { - active: active.value, - mode, - mode_blob_id: mode_id.value, - mode_blob: None, - vrr_enabled: vrr_enabled.value, - assigned_connector: DrmConnector::NONE, - gamma_lut: None, - gamma_lut_blob_id: gamma_lut.map_or(DrmBlob::NONE, |v| v.value), - gamma_lut_blob: None, - }; - Ok(MetalCrtc { - id: crtc, - idx, - master: master.clone(), - default_properties, - untyped_properties: RefCell::new(props.to_untyped()), - lease: Cell::new(None), - possible_planes, - connector: Default::default(), - pending_flip: Default::default(), - drm_state: RefCell::new(state), - active: active.id, - mode_id: mode_id.id, - vrr_enabled: vrr_enabled.id, - out_fence_ptr: out_fence_ptr.id, - gamma_lut: gamma_lut.map(|v| v.id), - gamma_lut_size, - sequence: Cell::new(0), - have_queued_sequence: Cell::new(false), - needs_vblank_emulation: Cell::new(false), - }) -} - -fn create_plane(plane: DrmPlane, master: &Rc) -> Result { - let info = master.get_plane_info(plane)?; - let props = collect_properties(master, plane)?; - let mut formats = AHashMap::new(); - if let Some((_, v)) = props.props.get(b"IN_FORMATS".as_bstr()) { - for format in master.get_in_formats(*v as _)? { - if format.modifiers.is_empty() { - continue; - } - if let Some(f) = crate::format::formats().get(&format.format) { - formats.insert( - format.format, - PlaneFormat { - format: f, - modifiers: format.modifiers, - }, - ); - } - } - } else { - for format in info.format_types { - if let Some(f) = crate::format::formats().get(&format) { - formats.insert( - format, - PlaneFormat { - format: f, - modifiers: indexset![INVALID_MODIFIER], - }, - ); - } - } - } - let ty = match props.props.get(b"type".as_bstr()) { - Some((def, val)) => match &def.ty { - DrmPropertyType::Enum { values, .. } => 'ty: { - for v in values { - if v.value == *val { - match v.name.as_bytes() { - b"Overlay" => break 'ty PlaneType::Overlay, - b"Primary" => break 'ty PlaneType::Primary, - b"Cursor" => break 'ty PlaneType::Cursor, - _ => return Err(DrmError::UnknownPlaneType(v.name.to_owned())), - } - } - } - return Err(DrmError::InvalidPlaneType(*val)); - } - _ => return Err(DrmError::InvalidPlaneTypeProperty), - }, - _ => { - return Err(DrmError::MissingProperty( - "type".to_string().into_boxed_str(), - )); - } - }; - let default_properties = create_default_properties( - &props, - &[ - ("AMD_PLANE_BLEND_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_BLEND_TF", DefaultValue::Enum("Default")), - ("AMD_PLANE_CTM", DefaultValue::Fixed(0)), - ("AMD_PLANE_DEGAMMA_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_HDR_MULT", DefaultValue::Fixed(0)), - ("AMD_PLANE_LUT3D", DefaultValue::Fixed(0)), - ("AMD_PLANE_SHAPER_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_SHAPER_TF", DefaultValue::Enum("Default")), - ("alpha", DefaultValue::RangeMax), - ("pixel blend mode", DefaultValue::Enum("Pre-multiplied")), - ("rotation", DefaultValue::Bitmask(&["rotate-0"])), - ], - ); - let fb_id = props.get("FB_ID")?.map(|v| DrmFb(v as _)); - let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); - let crtc_x = props.get("CRTC_X")?.map(|v| v as i32); - let crtc_y = props.get("CRTC_Y")?.map(|v| v as i32); - let crtc_w = props.get("CRTC_W")?.map(|v| v as i32); - let crtc_h = props.get("CRTC_H")?.map(|v| v as i32); - let src_x = props.get("SRC_X")?.map(|v| v as u32); - let src_y = props.get("SRC_Y")?.map(|v| v as u32); - let src_w = props.get("SRC_W")?.map(|v| v as u32); - let src_h = props.get("SRC_H")?.map(|v| v as u32); - let in_fence_fd = props.get("IN_FENCE_FD")?; - let state = DrmPlaneState { - fb_id: fb_id.value, - src_x: src_x.value, - src_y: src_y.value, - src_w: src_w.value, - src_h: src_h.value, - assigned_crtc: DrmCrtc::NONE, - crtc_id: crtc_id.value, - crtc_x: crtc_x.value, - crtc_y: crtc_y.value, - crtc_w: crtc_w.value, - crtc_h: crtc_h.value, - buffers: None, - }; - Ok(MetalPlane { - id: plane, - master: master.clone(), - default_properties, - untyped_properties: RefCell::new(props.to_untyped()), - ty, - possible_crtcs: info.possible_crtcs, - formats, - drm_state: RefCell::new(state), - fb_id: fb_id.id, - crtc_id: crtc_id.id, - crtc_x: crtc_x.id, - crtc_y: crtc_y.id, - crtc_w: crtc_w.id, - crtc_h: crtc_h.id, - src_x: src_x.id, - src_y: src_y.id, - src_w: src_w.id, - src_h: src_h.id, - in_fence_fd: in_fence_fd.id, - mode_w: Cell::new(0), - mode_h: Cell::new(0), - lease: Cell::new(None), - }) -} - - impl MetalBackend { pub fn check_render_context(&self, dev: &Rc) -> bool { let ctx = match self.ctx.get() { diff --git a/src/backends/metal/video/discovery.rs b/src/backends/metal/video/discovery.rs new file mode 100644 index 00000000..58a9a152 --- /dev/null +++ b/src/backends/metal/video/discovery.rs @@ -0,0 +1,660 @@ +use { + super::{ + ConnectorDisplayData, ConnectorFutures, FrontState, MetalConnector, MetalCrtc, + MetalDrmDevice, MetalEncoder, MetalPlane, PersistentDisplayData, PlaneFormat, PlaneType, + properties::{DefaultValue, collect_properties, create_default_properties}, + }, + crate::{ + async_engine::Phase, + backend::{ + BackendColorSpace, BackendConnectorState, BackendEotfs, BackendLuminance, + ConnectorKernelId, Mode, OutputId, + }, + backends::metal::{ + MetalBackend, + present::DEFAULT_PRE_COMMIT_MARGIN, + transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState}, + }, + cmm::cmm_primaries::Primaries, + edid::{CtaDataBlock, Descriptor, EdidExtension}, + format::XRGB8888, + utils::{ + binary_search_map::BinarySearchMap, bitflags::BitflagsExt, clonecell::CloneCell, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay, + numcell::NumCell, ordered_float::F64, + }, + video::{ + INVALID_MODIFIER, + drm::{ + ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, DrmCrtc, DrmEncoder, + DrmError, DrmFb, DrmMaster, DrmObject, DrmPlane, DrmPropertyType, + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, hdr_output_metadata, + }, + }, + }, + ahash::AHashMap, + bstr::ByteSlice, + indexmap::indexset, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, +}; + +pub(super) fn get_connectors( + backend: &Rc, + dev: &Rc, + ids: &[DrmConnector], +) -> Result< + ( + CopyHashMap>, + CopyHashMap, + ), + DrmError, +> { + let connectors = CopyHashMap::new(); + let futures = CopyHashMap::new(); + for connector in ids { + match create_connector(backend, *connector, dev) { + Ok((con, fut)) => { + let id = con.id; + connectors.set(id, con); + futures.set(id, fut); + } + Err(e) => return Err(DrmError::CreateConnector(Box::new(e))), + } + } + Ok((connectors, futures)) +} + +pub(super) fn create_connector( + backend: &Rc, + connector: DrmConnector, + dev: &Rc, +) -> Result<(Rc, ConnectorFutures), DrmError> { + let display = create_connector_display_data(connector, dev)?; + log::info!( + "Creating connector {} for device {}", + display.connector_id, + dev.devnode.as_bytes().as_bstr(), + ); + let slf = Rc::new(MetalConnector { + id: connector, + kernel_id: Cell::new(display.connector_id), + master: dev.master.clone(), + state: backend.state.clone(), + dev: dev.clone(), + backend: backend.clone(), + connector_id: backend.state.connector_ids.next(), + buffers: Default::default(), + color_description: CloneCell::new(backend.state.color_manager.srgb_gamma22().clone()), + lease: Cell::new(None), + buffers_idle: Cell::new(true), + crtc_idle: Cell::new(true), + has_damage: NumCell::new(1), + primary_plane: Default::default(), + cursor_plane: Default::default(), + crtc: Default::default(), + on_change: Default::default(), + present_trigger: Default::default(), + cursor_x: Cell::new(0), + cursor_y: Cell::new(0), + cursor_enabled: Cell::new(false), + cursor_buffers: Default::default(), + display: RefCell::new(display), + frontend_state: Cell::new(FrontState::Removed), + cursor_changed: Cell::new(false), + cursor_damage: Cell::new(false), + cursor_swap_buffer: Cell::new(false), + cursor_sync: Default::default(), + drm_feedback: Default::default(), + scanout_buffers: Default::default(), + active_framebuffer: Default::default(), + next_framebuffer: Default::default(), + direct_scanout_active: Cell::new(false), + next_vblank_nsec: Cell::new(0), + version: Default::default(), + expected_sequence: Default::default(), + pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), + pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), + post_commit_margin_decay: GeometricDecay::new(0.1, dev.min_post_commit_margin.get()), + post_commit_margin: Cell::new(dev.min_post_commit_margin.get()), + vblank_miss_sec: Cell::new(0), + vblank_miss_this_sec: Default::default(), + presentation_is_sync: Cell::new(false), + presentation_is_zero_copy: Cell::new(false), + }); + let futures = ConnectorFutures { + _present: backend.state.eng.spawn2( + "present loop", + Phase::Present, + slf.clone().present_loop(), + ), + }; + Ok((slf, futures)) +} + +pub(super) fn create_connector_display_data( + connector: DrmConnector, + dev: &Rc, +) -> Result { + let info = dev.master.get_connector_info(connector, true)?; + let mut crtcs = BinarySearchMap::new(); + for encoder in info.encoders { + if let Some(encoder) = dev.encoders.get(&encoder) { + for (_, crtc) in &encoder.crtcs { + crtcs.insert(crtc.id, crtc.clone()); + } + } + } + let props = collect_properties(&dev.master, connector)?; + let connection = ConnectorStatus::from_drm(info.connection); + let mut name = String::new(); + let mut manufacturer = String::new(); + let mut serial_number = String::new(); + let mut vrr_refresh_max_nsec = u64::MAX; + let connector_id = ConnectorKernelId { + ty: ConnectorType::from_drm(info.connector_type), + idx: info.connector_type_id, + }; + let mut supports_bt2020 = false; + let mut supports_pq = false; + let mut luminance = None; + let mut primaries = Primaries::SRGB; + 'fetch_edid: { + if connection != ConnectorStatus::Connected { + break 'fetch_edid; + } + let edid = match props.get("EDID") { + Ok(e) => e, + _ => { + log::warn!( + "Connector {} is connected but has no EDID blob", + connector_id, + ); + break 'fetch_edid; + } + }; + let blob = match dev.master.getblob_vec::(DrmBlob(edid.value as _)) { + Ok(b) => b, + Err(e) => { + log::error!( + "Could not fetch edid property of connector {}: {}", + connector_id, + ErrorFmt(e) + ); + break 'fetch_edid; + } + }; + let edid = match crate::edid::parse(&blob) { + Ok(e) => e, + Err(e) => { + log::error!( + "Could not parse edid property of connector {}: {}", + connector_id, + ErrorFmt(e) + ); + break 'fetch_edid; + } + }; + manufacturer = edid.base_block.id_manufacturer_name.to_string(); + for descriptor in edid.base_block.descriptors.iter().flatten() { + match descriptor { + Descriptor::DisplayProductSerialNumber(s) => { + serial_number.clone_from(s); + } + Descriptor::DisplayProductName(s) => { + name.clone_from(s); + } + _ => {} + } + } + if name.is_empty() { + log::warn!( + "The display attached to connector {} does not have a product name descriptor", + connector_id, + ); + } + if serial_number.is_empty() { + log::warn!( + "The display attached to connector {} does not have a serial number descriptor", + connector_id, + ); + serial_number = edid.base_block.id_serial_number.to_string(); + } + let min_vrr_hz = 'fetch_min_hz: { + for ext in &edid.extension_blocks { + if let EdidExtension::CtaV3(cta) = ext { + for data_block in &cta.data_blocks { + if let CtaDataBlock::VendorAmd(amd) = data_block { + break 'fetch_min_hz amd.minimum_refresh_hz as u64; + } + } + } + } + for desc in &edid.base_block.descriptors { + if let Some(desc) = desc + && let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc + { + break 'fetch_min_hz timings.vertical_field_rate_min as u64; + } + } + 0 + }; + if min_vrr_hz > 0 { + vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz; + } + let cc = &edid.base_block.chromaticity_coordinates; + let map = |c: u16| F64(c as f64 / 1024.0); + primaries = Primaries { + r: (map(cc.red_x), map(cc.red_y)), + g: (map(cc.green_x), map(cc.green_y)), + b: (map(cc.blue_x), map(cc.blue_y)), + wp: (map(cc.white_x), map(cc.white_y)), + }; + for ext in &edid.extension_blocks { + if let EdidExtension::CtaV3(cta) = ext { + for data_block in &cta.data_blocks { + match data_block { + CtaDataBlock::Colorimetry(c) => { + if c.bt2020_rgb { + supports_bt2020 = true; + } + } + CtaDataBlock::StaticHdrMetadata(h) => { + if h.smpte_st_2084 { + supports_pq = true; + } + if let Some(max) = h.max_luminance { + luminance = Some(BackendLuminance { + min: h.min_luminance.unwrap_or(0.0), + max, + max_fall: h.max_luminance.unwrap_or(max), + }); + } + } + _ => {} + } + } + } + } + } + let output_id = OutputId::new(connector_id.to_string(), manufacturer, name, serial_number); + let first_mode = info + .modes + .first() + .cloned() + .map(|m| m.to_backend()) + .unwrap_or_default(); + let persistent = match dev.backend.persistent_display_data.get(&output_id) { + Some(ds) => { + if connection != ConnectorStatus::Disconnected { + log::info!("Reusing desired state for {:?}", output_id); + } + ds + } + None => { + let ds = Rc::new(PersistentDisplayData { + state: RefCell::new(BackendConnectorState { + serial: dev.backend.state.backend_connector_state_serials.next(), + enabled: true, + active: true, + mode: first_mode, + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + eotf: Default::default(), + gamma_lut: Default::default(), + }), + }); + dev.backend + .persistent_display_data + .set(output_id.clone(), ds.clone()); + ds + } + }; + let mut desired_state = persistent.state.borrow_mut(); + if desired_state.mode == Mode::default() { + desired_state.mode = first_mode; + } else if info + .modes + .iter() + .all(|m| m.to_backend() != desired_state.mode) + { + log::warn!("Discarding previously desired mode"); + desired_state.mode = first_mode; + } + let non_desktop = props.get("non-desktop")?.value != 0; + let vrr_capable = match props.get("vrr_capable") { + Ok(c) => c.value == 1, + Err(_) => false, + }; + if !vrr_capable && desired_state.vrr { + log::warn!("Connector has lost VRR capability"); + desired_state.vrr = false; + } + { + let viable = match desired_state.eotf { + BackendEotfs::Default => true, + BackendEotfs::Pq => supports_pq, + }; + if !viable { + log::warn!("Discarding previously desired EOTF"); + desired_state.eotf = BackendEotfs::Default; + } + } + { + let viable = match desired_state.color_space { + BackendColorSpace::Default => true, + BackendColorSpace::Bt2020 => supports_bt2020, + }; + if !viable { + log::warn!("Discarding previously desired color space"); + desired_state.color_space = BackendColorSpace::Default; + } + } + drop(desired_state); + let default_properties = create_default_properties( + &props, + &[ + ("Broadcast RGB", DefaultValue::Enum("Automatic")), + ("HDR_SOURCE_METADATA", DefaultValue::Fixed(0)), + ("Output format", DefaultValue::Enum("Default")), + ("WRITEBACK_FB_ID", DefaultValue::Fixed(0)), + ("WRITEBACK_OUT_FENCE_PTR", DefaultValue::Fixed(0)), + ("content type", DefaultValue::Enum("No Data")), + ("dither", DefaultValue::Enum("off")), + ("max bpc", DefaultValue::RangeMax), + ], + ); + let hdr_metadata_prop = props + .get("HDR_OUTPUT_METADATA") + .map(|p| p.map(|v| DrmBlob(v as _))) + .ok(); + let mut hdr_metadata = None; + let mut hdr_metadata_blob_id = DrmBlob::NONE; + if let Some(p) = &hdr_metadata_prop { + hdr_metadata_blob_id = p.value; + hdr_metadata = Some(hdr_output_metadata::from_eotf( + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, + )); + if p.value.is_some() { + match dev.master.getblob::(p.value) { + Ok(m) => hdr_metadata = Some(m), + _ => { + log::debug!("Could not retrieve hdr output metadata"); + } + } + } + } + let colorspace_prop = props.get("Colorspace").ok(); + let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); + let drm_state = DrmConnectorState { + crtc_id: crtc_id.value, + color_space: colorspace_prop.map(|p| p.value), + hdr_metadata, + hdr_metadata_blob_id, + hdr_metadata_blob: None, + locked: true, + fb: DrmFb::NONE, + fb_idx: 0, + cursor_fb: DrmFb::NONE, + cursor_fb_idx: 0, + cursor_x: 0, + cursor_y: 0, + out_fd: None, + src_w: 0, + src_h: 0, + crtc_x: 0, + crtc_y: 0, + crtc_w: 0, + crtc_h: 0, + }; + Ok(ConnectorDisplayData { + crtc_id: props.get("CRTC_ID")?.id, + crtcs, + first_mode, + modes: info.modes, + persistent, + refresh: 0, + non_desktop, + non_desktop_effective: non_desktop, + vrr_capable, + _vrr_refresh_max_nsec: vrr_refresh_max_nsec, + default_properties, + untyped_properties: props.to_untyped(), + connection, + mm_width: info.mm_width, + mm_height: info.mm_height, + _subpixel: info.subpixel, + supports_bt2020, + supports_pq, + primaries, + luminance, + connector_id, + output_id, + colorspace: colorspace_prop.map(|p| p.id), + hdr_metadata: hdr_metadata_prop.map(|p| p.id), + drm_state, + }) +} + +pub(super) fn create_encoder( + encoder: DrmEncoder, + master: &Rc, + crtcs: &AHashMap>, +) -> Result { + let info = master.get_encoder_info(encoder)?; + let mut possible = AHashMap::new(); + for crtc in crtcs.values() { + if info.possible_crtcs.contains(1 << crtc.idx) { + possible.insert(crtc.id, crtc.clone()); + } + } + Ok(MetalEncoder { + id: encoder, + crtcs: possible, + }) +} + +pub(super) fn create_crtc( + crtc: DrmCrtc, + idx: usize, + master: &Rc, + planes: &AHashMap>, +) -> Result { + let mask = 1 << idx; + let mut possible_planes = BinarySearchMap::new(); + for plane in planes.values() { + if plane.possible_crtcs.contains(mask) { + possible_planes.insert(plane.id, plane.clone()); + } + } + let props = collect_properties(master, crtc)?; + let default_properties = create_default_properties( + &props, + &[ + ("AMD_CRTC_REGAMMA_TF", DefaultValue::Enum("Default")), + ("CTM", DefaultValue::Fixed(0)), + ("DEGAMMA_LUT", DefaultValue::Fixed(0)), + ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), + ], + ); + let active = props.get("ACTIVE")?.map(|v| v == 1); + let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); + let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); + let out_fence_ptr = props.get("OUT_FENCE_PTR")?; + let gamma_lut = props + .get("GAMMA_LUT") + .ok() + .map(|v| v.map(|v| DrmBlob(v as u32))); + let mut gamma_lut_size = None; + if gamma_lut.is_some() { + gamma_lut_size = props.get("GAMMA_LUT_SIZE").ok().map(|v| v.value as u32); + } + let mut mode = None; + if mode_id.value.is_some() { + match master.getblob::(mode_id.value) { + Ok(m) => mode = Some(m.into()), + _ => { + log::debug!("Could not retrieve current mode of connector"); + } + } + } + let state = DrmCrtcState { + active: active.value, + mode, + mode_blob_id: mode_id.value, + mode_blob: None, + vrr_enabled: vrr_enabled.value, + assigned_connector: DrmConnector::NONE, + gamma_lut: None, + gamma_lut_blob_id: gamma_lut.map_or(DrmBlob::NONE, |v| v.value), + gamma_lut_blob: None, + }; + Ok(MetalCrtc { + id: crtc, + idx, + master: master.clone(), + default_properties, + untyped_properties: RefCell::new(props.to_untyped()), + lease: Cell::new(None), + possible_planes, + connector: Default::default(), + pending_flip: Default::default(), + drm_state: RefCell::new(state), + active: active.id, + mode_id: mode_id.id, + vrr_enabled: vrr_enabled.id, + out_fence_ptr: out_fence_ptr.id, + gamma_lut: gamma_lut.map(|v| v.id), + gamma_lut_size, + sequence: Cell::new(0), + have_queued_sequence: Cell::new(false), + needs_vblank_emulation: Cell::new(false), + }) +} + +pub(super) fn create_plane(plane: DrmPlane, master: &Rc) -> Result { + let info = master.get_plane_info(plane)?; + let props = collect_properties(master, plane)?; + let mut formats = AHashMap::new(); + if let Some((_, v)) = props.props.get(b"IN_FORMATS".as_bstr()) { + for format in master.get_in_formats(*v as _)? { + if format.modifiers.is_empty() { + continue; + } + if let Some(f) = crate::format::formats().get(&format.format) { + formats.insert( + format.format, + PlaneFormat { + format: f, + modifiers: format.modifiers, + }, + ); + } + } + } else { + for format in info.format_types { + if let Some(f) = crate::format::formats().get(&format) { + formats.insert( + format, + PlaneFormat { + format: f, + modifiers: indexset![INVALID_MODIFIER], + }, + ); + } + } + } + let ty = match props.props.get(b"type".as_bstr()) { + Some((def, val)) => match &def.ty { + DrmPropertyType::Enum { values, .. } => 'ty: { + for v in values { + if v.value == *val { + match v.name.as_bytes() { + b"Overlay" => break 'ty PlaneType::Overlay, + b"Primary" => break 'ty PlaneType::Primary, + b"Cursor" => break 'ty PlaneType::Cursor, + _ => return Err(DrmError::UnknownPlaneType(v.name.to_owned())), + } + } + } + return Err(DrmError::InvalidPlaneType(*val)); + } + _ => return Err(DrmError::InvalidPlaneTypeProperty), + }, + _ => { + return Err(DrmError::MissingProperty( + "type".to_string().into_boxed_str(), + )); + } + }; + let default_properties = create_default_properties( + &props, + &[ + ("AMD_PLANE_BLEND_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_BLEND_TF", DefaultValue::Enum("Default")), + ("AMD_PLANE_CTM", DefaultValue::Fixed(0)), + ("AMD_PLANE_DEGAMMA_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_HDR_MULT", DefaultValue::Fixed(0)), + ("AMD_PLANE_LUT3D", DefaultValue::Fixed(0)), + ("AMD_PLANE_SHAPER_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_SHAPER_TF", DefaultValue::Enum("Default")), + ("alpha", DefaultValue::RangeMax), + ("pixel blend mode", DefaultValue::Enum("Pre-multiplied")), + ("rotation", DefaultValue::Bitmask(&["rotate-0"])), + ], + ); + let fb_id = props.get("FB_ID")?.map(|v| DrmFb(v as _)); + let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); + let crtc_x = props.get("CRTC_X")?.map(|v| v as i32); + let crtc_y = props.get("CRTC_Y")?.map(|v| v as i32); + let crtc_w = props.get("CRTC_W")?.map(|v| v as i32); + let crtc_h = props.get("CRTC_H")?.map(|v| v as i32); + let src_x = props.get("SRC_X")?.map(|v| v as u32); + let src_y = props.get("SRC_Y")?.map(|v| v as u32); + let src_w = props.get("SRC_W")?.map(|v| v as u32); + let src_h = props.get("SRC_H")?.map(|v| v as u32); + let in_fence_fd = props.get("IN_FENCE_FD")?; + let state = DrmPlaneState { + fb_id: fb_id.value, + src_x: src_x.value, + src_y: src_y.value, + src_w: src_w.value, + src_h: src_h.value, + assigned_crtc: DrmCrtc::NONE, + crtc_id: crtc_id.value, + crtc_x: crtc_x.value, + crtc_y: crtc_y.value, + crtc_w: crtc_w.value, + crtc_h: crtc_h.value, + buffers: None, + }; + Ok(MetalPlane { + id: plane, + master: master.clone(), + default_properties, + untyped_properties: RefCell::new(props.to_untyped()), + ty, + possible_crtcs: info.possible_crtcs, + formats, + drm_state: RefCell::new(state), + fb_id: fb_id.id, + crtc_id: crtc_id.id, + crtc_x: crtc_x.id, + crtc_y: crtc_y.id, + crtc_w: crtc_w.id, + crtc_h: crtc_h.id, + src_x: src_x.id, + src_y: src_y.id, + src_w: src_w.id, + src_h: src_h.id, + in_fence_fd: in_fence_fd.id, + mode_w: Cell::new(0), + mode_h: Cell::new(0), + lease: Cell::new(None), + }) +}