use drm::Device as _;
use drm::control::Device as _;
use drmkit_core::{Device, ObjectType, PropertyStore, Result};
use drmkit_fmt::FormatTable;
use crate::registry::{
BlendModeValues, ColorEncodingValues, ColorPipeline, ColorRangeValues, PlaneCapabilities,
PlaneRegistry, PlaneType,
};
const PLANE_TYPE_OVERLAY: u64 = 0;
const PLANE_TYPE_PRIMARY: u64 = 1;
const PLANE_TYPE_CURSOR: u64 = 2;
impl PlaneRegistry {
pub fn probe(device: &Device) -> Result<Self> {
let handles = device
.plane_handles()
.map_err(|error| drmkit_core::CoreError::from(io_errno(&error)))?;
let resources = device
.resource_handles()
.map_err(|error| drmkit_core::CoreError::from(io_errno(&error)))?;
let cursor_cap = |cap| {
device
.get_driver_capability(cap)
.ok()
.and_then(|value| u32::try_from(value).ok())
.unwrap_or(0)
};
let cursor_max = (
cursor_cap(drm::DriverCapability::CursorWidth),
cursor_cap(drm::DriverCapability::CursorHeight),
);
let mut capabilities = Vec::with_capacity(handles.len());
for handle in handles {
let Ok(plane) = device.get_plane(handle) else {
continue;
};
let id: u32 = handle.into();
let mut properties = PropertyStore::new();
if properties
.cache_properties(device, id, ObjectType::Plane)
.is_err()
{
continue;
}
capabilities.push(probe_plane(
device,
&resources,
&properties,
id,
&plane,
cursor_max,
));
}
let mut registry = Self::from_capabilities(capabilities);
for (index, crtc) in resources.crtcs().iter().enumerate() {
let Ok(index) = u32::try_from(index) else {
break;
};
registry.set_blend_stage_limit(index, blend_stage_limit(device, (*crtc).into()));
}
Ok(registry)
}
}
fn probe_plane(
device: &Device,
resources: &drm::control::ResourceHandles,
properties: &PropertyStore,
id: u32,
plane: &drm::control::plane::Info,
cursor_max: (u32, u32),
) -> PlaneCapabilities {
let value = |name: &str| properties.property_value(id, name).ok();
let present = |name: &str| properties.property_id(id, name).is_ok();
let enum_value =
|name: &str, variant: &str| properties.enum_value(device, id, name, variant).ok();
let (zpos_min, zpos_max) = match properties.range(id, "zpos") {
Ok(Some((min, max))) => (Some(min), Some(max)),
Ok(None) => {
let fixed = value("zpos");
(fixed, fixed)
}
Err(_) => (None, None),
};
let format_table = in_formats(device, properties, id);
let has_format_modifiers = !format_table.is_empty();
let plane_type = match value("type") {
Some(PLANE_TYPE_PRIMARY) => PlaneType::Primary,
Some(PLANE_TYPE_CURSOR) => PlaneType::Cursor,
Some(PLANE_TYPE_OVERLAY | _) | None => PlaneType::Overlay,
};
let (cursor_max_w, cursor_max_h) = if plane_type == PlaneType::Cursor {
cursor_max
} else {
(0, 0)
};
PlaneCapabilities {
id,
possible_crtcs: crtc_mask(resources, plane.possible_crtcs()),
plane_type,
formats: plane.formats().to_vec(),
format_table,
has_format_modifiers,
zpos_min,
zpos_max,
multirect_parent: multirect_parent(device, properties, id),
rotation_bits: properties.bitmask_bits(device, id, "rotation").unwrap_or(0),
supports_rotation: present("rotation"),
supports_scaling: present("SRC_W"),
has_pixel_blend_mode: present("pixel blend mode"),
has_fb_damage_clips: present("FB_DAMAGE_CLIPS"),
has_per_plane_alpha: present("alpha"),
blend_modes: BlendModeValues {
premultiplied: enum_value("pixel blend mode", "Pre-multiplied"),
coverage: enum_value("pixel blend mode", "Coverage"),
none: enum_value("pixel blend mode", "None"),
},
has_color_encoding: present("COLOR_ENCODING"),
color_encodings: ColorEncodingValues {
bt601: enum_value("COLOR_ENCODING", "ITU-R BT.601 YCbCr"),
bt709: enum_value("COLOR_ENCODING", "ITU-R BT.709 YCbCr"),
bt2020: enum_value("COLOR_ENCODING", "ITU-R BT.2020 YCbCr"),
},
has_color_range: present("COLOR_RANGE"),
color_ranges: ColorRangeValues {
limited: enum_value("COLOR_RANGE", "YCbCr limited range"),
full: enum_value("COLOR_RANGE", "YCbCr full range"),
},
color_pipeline: ColorPipeline {
has_degamma_lut: present("PLANE_DEGAMMA_LUT"),
has_ctm: present("PLANE_CTM"),
has_hdr_mult: present("PLANE_HDR_MULT"),
degamma_lut_size: value("PLANE_DEGAMMA_LUT_SIZE")
.and_then(|size| u32::try_from(size).ok())
.unwrap_or(0),
},
cursor_max_w,
cursor_max_h,
modifier_classes: Vec::new(),
}
}
fn in_formats(device: &Device, properties: &PropertyStore, id: u32) -> FormatTable {
let Ok(blob_id) = properties.property_value(id, "IN_FORMATS") else {
return FormatTable::default();
};
device
.property_blob(blob_id)
.map(|blob| FormatTable::from_blob(&blob))
.unwrap_or_default()
}
fn multirect_parent(device: &Device, properties: &PropertyStore, id: u32) -> Option<u32> {
let blob_id = properties.property_value(id, "capabilities").ok()?;
if blob_id == 0 {
return None;
}
let blob = device.property_blob(blob_id).ok()?;
crate::parse_multirect_parent(&blob)
}
fn blend_stage_limit(device: &Device, crtc_id: u32) -> Option<usize> {
let mut properties = PropertyStore::new();
properties
.cache_properties(device, crtc_id, ObjectType::Crtc)
.ok()?;
let blob_id = properties.property_value(crtc_id, "capabilities").ok()?;
if blob_id == 0 {
return None;
}
let blob = device.property_blob(blob_id).ok()?;
crate::parse_blend_stage_limit(&blob)
}
fn io_errno(error: &std::io::Error) -> rustix::io::Errno {
rustix::io::Errno::from_io_error(error).unwrap_or(rustix::io::Errno::IO)
}
fn crtc_mask(
resources: &drm::control::ResourceHandles,
filter: drm::control::CrtcListFilter,
) -> u32 {
let selected = resources.filter_crtcs(filter);
resources
.crtcs()
.iter()
.enumerate()
.filter(|(_, handle)| selected.contains(handle))
.fold(0_u32, |mask, (index, _)| {
mask | (1_u32
.checked_shl(u32::try_from(index).unwrap_or(u32::MAX))
.unwrap_or(0))
})
}