1
0
Fork 0
forked from wry/wry

metal: split video discovery

This commit is contained in:
kossLAN 2026-05-29 21:27:55 -04:00
parent 556e4214c4
commit 7531c8f791
No known key found for this signature in database
2 changed files with 677 additions and 642 deletions

View file

@ -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,
present::{DEFAULT_POST_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA},
transaction::MetalDeviceTransaction,
},
transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState, 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<MetalBackend>,
dev: &Rc<MetalDrmDevice>,
ids: &[DrmConnector],
) -> Result<
(
CopyHashMap<DrmConnector, Rc<MetalConnector>>,
CopyHashMap<DrmConnector, ConnectorFutures>,
),
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<MetalBackend>,
connector: DrmConnector,
dev: &Rc<MetalDrmDevice>,
) -> Result<(Rc<MetalConnector>, 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<MetalDrmDevice>,
) -> Result<ConnectorDisplayData, DrmError> {
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::<u8>(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::<hdr_output_metadata>(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<DrmMaster>,
crtcs: &AHashMap<DrmCrtc, Rc<MetalCrtc>>,
) -> Result<MetalEncoder, DrmError> {
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<DrmMaster>,
planes: &AHashMap<DrmPlane, Rc<MetalPlane>>,
) -> Result<MetalCrtc, DrmError> {
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::<drm_mode_modeinfo>(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<DrmMaster>) -> Result<MetalPlane, DrmError> {
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<MetalDrmDevice>) -> bool {
let ctx = match self.ctx.get() {

View file

@ -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<MetalBackend>,
dev: &Rc<MetalDrmDevice>,
ids: &[DrmConnector],
) -> Result<
(
CopyHashMap<DrmConnector, Rc<MetalConnector>>,
CopyHashMap<DrmConnector, ConnectorFutures>,
),
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<MetalBackend>,
connector: DrmConnector,
dev: &Rc<MetalDrmDevice>,
) -> Result<(Rc<MetalConnector>, 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<MetalDrmDevice>,
) -> Result<ConnectorDisplayData, DrmError> {
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::<u8>(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::<hdr_output_metadata>(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<DrmMaster>,
crtcs: &AHashMap<DrmCrtc, Rc<MetalCrtc>>,
) -> Result<MetalEncoder, DrmError> {
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<DrmMaster>,
planes: &AHashMap<DrmPlane, Rc<MetalPlane>>,
) -> Result<MetalCrtc, DrmError> {
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::<drm_mode_modeinfo>(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<DrmMaster>) -> Result<MetalPlane, DrmError> {
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),
})
}