metal: split video discovery
This commit is contained in:
parent
556e4214c4
commit
7531c8f791
2 changed files with 677 additions and 642 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
mod copy_device;
|
mod copy_device;
|
||||||
|
mod discovery;
|
||||||
mod hardware_cursor;
|
mod hardware_cursor;
|
||||||
mod lease;
|
mod lease;
|
||||||
mod model;
|
mod model;
|
||||||
|
|
@ -18,19 +19,22 @@ pub use {
|
||||||
properties::{DefaultProperty, TypedProperty},
|
properties::{DefaultProperty, TypedProperty},
|
||||||
};
|
};
|
||||||
|
|
||||||
use properties::{
|
use {
|
||||||
DefaultValue, collect_properties, collect_untyped_properties, create_default_properties,
|
discovery::{
|
||||||
|
create_connector, create_connector_display_data, create_crtc, create_encoder, create_plane,
|
||||||
|
get_connectors,
|
||||||
|
},
|
||||||
|
properties::collect_untyped_properties,
|
||||||
};
|
};
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
async_engine::Phase,
|
|
||||||
backend::{
|
backend::{
|
||||||
BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLessee,
|
BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLessee,
|
||||||
BackendEotfs, BackendEvent, BackendGammaLut, BackendGammaLutElement,
|
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,
|
Connector, ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId,
|
||||||
Mode, MonitorInfo, OutputId,
|
MonitorInfo,
|
||||||
transaction::{
|
transaction::{
|
||||||
BackendConnectorTransaction, BackendConnectorTransactionError,
|
BackendConnectorTransaction, BackendConnectorTransactionError,
|
||||||
BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn,
|
BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn,
|
||||||
|
|
@ -38,42 +42,33 @@ use {
|
||||||
},
|
},
|
||||||
backends::metal::{
|
backends::metal::{
|
||||||
MetalBackend, MetalError,
|
MetalBackend, MetalError,
|
||||||
present::{
|
present::{DEFAULT_POST_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA},
|
||||||
DEFAULT_POST_COMMIT_MARGIN, DEFAULT_PRE_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA,
|
transaction::MetalDeviceTransaction,
|
||||||
},
|
|
||||||
transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState, MetalDeviceTransaction},
|
|
||||||
},
|
},
|
||||||
cmm::cmm_primaries::Primaries,
|
|
||||||
drm_feedback::DrmFeedback,
|
drm_feedback::DrmFeedback,
|
||||||
edid::{CtaDataBlock, Descriptor, EdidExtension},
|
|
||||||
format::XRGB8888,
|
format::XRGB8888,
|
||||||
gfx_api::GfxApi,
|
gfx_api::GfxApi,
|
||||||
ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY},
|
ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY},
|
||||||
tree::OutputNode,
|
tree::OutputNode,
|
||||||
udev::UdevDevice,
|
udev::UdevDevice,
|
||||||
utils::{
|
utils::{
|
||||||
binary_search_map::BinarySearchMap, bitflags::BitflagsExt, cell_ext::CellExt,
|
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||||
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
errorfmt::ErrorFmt, oserror::OsError,
|
||||||
geometric_decay::GeometricDecay, numcell::NumCell, ordered_float::F64,
|
|
||||||
oserror::OsError,
|
|
||||||
},
|
},
|
||||||
video::{
|
video::{
|
||||||
INVALID_MODIFIER,
|
|
||||||
drm::{
|
drm::{
|
||||||
ConnectorStatus, ConnectorType, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources,
|
ConnectorStatus, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources, DrmConnector,
|
||||||
DrmConnector, DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFb, DrmMaster,
|
DrmCrtc, DrmError, DrmEvent, DrmFb, DrmMaster, DrmObject, DrmProperty,
|
||||||
DrmObject, DrmPlane, DrmProperty, DrmPropertyType, DrmVersion,
|
DrmVersion, drm_mode_modeinfo, hdr_output_metadata,
|
||||||
HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, hdr_output_metadata,
|
|
||||||
},
|
},
|
||||||
gbm::GbmDevice,
|
gbm::GbmDevice,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ahash::{AHashMap, AHashSet},
|
ahash::{AHashMap, AHashSet},
|
||||||
bstr::ByteSlice,
|
bstr::ByteSlice,
|
||||||
indexmap::indexset,
|
|
||||||
isnt::std_1::collections::IsntHashMapExt,
|
isnt::std_1::collections::IsntHashMapExt,
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::Cell,
|
||||||
collections::hash_map::Entry,
|
collections::hash_map::Entry,
|
||||||
mem,
|
mem,
|
||||||
ops::DerefMut,
|
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 {
|
impl MetalBackend {
|
||||||
pub fn check_render_context(&self, dev: &Rc<MetalDrmDevice>) -> bool {
|
pub fn check_render_context(&self, dev: &Rc<MetalDrmDevice>) -> bool {
|
||||||
let ctx = match self.ctx.get() {
|
let ctx = match self.ctx.get() {
|
||||||
|
|
|
||||||
660
src/backends/metal/video/discovery.rs
Normal file
660
src/backends/metal/video/discovery.rs
Normal 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),
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue