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 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<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() {
|
||||
|
|
|
|||
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