use drmkit_fmt::{BandwidthClass, FormatTable, Modifier, classify};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[non_exhaustive]
pub enum PlaneType {
Primary,
#[default]
Overlay,
Cursor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(
clippy::exhaustive_enums,
reason = "drmkit-scene resolves it with an exhaustive match, so a new encoding fails to compile until it is mapped"
)]
pub enum ColorEncoding {
Bt601,
Bt709,
Bt2020,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(
clippy::exhaustive_enums,
reason = "a closed set: there is no further variant to add"
)]
pub enum ColorRange {
Limited,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct BlendModeValues {
pub premultiplied: Option<u64>,
pub coverage: Option<u64>,
pub none: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct ColorEncodingValues {
pub bt601: Option<u64>,
pub bt709: Option<u64>,
pub bt2020: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct ColorRangeValues {
pub limited: Option<u64>,
pub full: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct ColorPipeline {
pub has_degamma_lut: bool,
pub has_ctm: bool,
pub has_hdr_mult: bool,
pub degamma_lut_size: u32,
}
#[expect(
clippy::struct_excessive_bools,
reason = "one flag per kernel property; see the type documentation"
)]
#[derive(Debug, Clone, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct PlaneCapabilities {
pub id: u32,
pub possible_crtcs: u32,
pub plane_type: PlaneType,
pub formats: Vec<u32>,
pub format_table: FormatTable,
pub zpos_min: Option<u64>,
pub zpos_max: Option<u64>,
pub multirect_parent: Option<u32>,
pub rotation_bits: u64,
pub supports_rotation: bool,
pub supports_scaling: bool,
pub has_format_modifiers: bool,
pub has_pixel_blend_mode: bool,
pub has_fb_damage_clips: bool,
pub has_per_plane_alpha: bool,
pub blend_modes: BlendModeValues,
pub has_color_encoding: bool,
pub color_encodings: ColorEncodingValues,
pub has_color_range: bool,
pub color_ranges: ColorRangeValues,
pub color_pipeline: ColorPipeline,
pub cursor_max_w: u32,
pub cursor_max_h: u32,
pub modifier_classes: Vec<(u64, BandwidthClass)>,
}
impl PlaneCapabilities {
#[must_use]
pub fn supports_format(&self, fourcc: u32) -> bool {
self.formats.contains(&fourcc)
}
#[must_use]
pub fn scans_out(&self, fourcc: u32, modifier: Modifier) -> bool {
if self.format_table.is_empty() {
self.supports_format(fourcc)
} else {
drmkit_fmt::layer_fits_plane(&self.format_table, fourcc, modifier)
}
}
#[must_use]
pub fn scans_out_linear(&self, fourcc: u32) -> bool {
self.scans_out(fourcc, Modifier::LINEAR)
}
#[must_use]
pub const fn compatible_with_crtc(&self, crtc_index: u32) -> bool {
if crtc_index >= u32::BITS {
return false;
}
self.possible_crtcs & (1 << crtc_index) != 0
}
#[must_use]
pub fn bandwidth_class(&self, modifier: u64) -> BandwidthClass {
self.modifier_classes
.binary_search_by_key(&modifier, |(m, _)| *m)
.map_or_else(
|_| classify(Modifier(modifier)),
|index| self.modifier_classes[index].1,
)
}
pub fn build_format_metadata(&mut self) {
if self.format_table.is_empty() && !self.formats.is_empty() {
self.format_table = FormatTable::from_pairs(
self.formats
.iter()
.map(|&fourcc| (fourcc, Modifier::LINEAR)),
);
}
self.modifier_classes.clear();
self.modifier_classes.extend(
self.format_table
.all()
.iter()
.map(|fm| (fm.modifier.0, classify(fm.modifier))),
);
self.modifier_classes.sort_unstable_by_key(|(m, _)| *m);
self.modifier_classes.dedup_by_key(|(m, _)| *m);
}
#[must_use]
pub fn distinct_modifier_count(&self) -> usize {
self.modifier_classes.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct PlaneRegistry {
planes: Vec<PlaneCapabilities>,
stage_limits: Vec<Option<usize>>,
}
impl PlaneRegistry {
#[must_use]
pub const fn new() -> Self {
Self {
planes: Vec::new(),
stage_limits: Vec::new(),
}
}
#[must_use]
pub fn from_capabilities(mut capabilities: Vec<PlaneCapabilities>) -> Self {
for plane in &mut capabilities {
plane.build_format_metadata();
}
Self {
planes: capabilities,
stage_limits: Vec::new(),
}
}
#[must_use]
pub fn blend_stage_limit(&self, crtc_index: u32) -> Option<usize> {
self.stage_limits
.get(usize::try_from(crtc_index).ok()?)
.copied()
.flatten()
}
pub fn set_blend_stage_limit(&mut self, crtc_index: u32, limit: Option<usize>) {
let Ok(index) = usize::try_from(crtc_index) else {
return;
};
if self.stage_limits.len() <= index {
self.stage_limits.resize(index + 1, None);
}
self.stage_limits[index] = limit;
}
#[must_use]
pub fn all(&self) -> &[PlaneCapabilities] {
&self.planes
}
pub fn for_crtc(&self, crtc_index: u32) -> impl Iterator<Item = &PlaneCapabilities> + '_ {
self.planes
.iter()
.filter(move |plane| plane.compatible_with_crtc(crtc_index))
}
#[must_use]
pub fn candidate_modifiers(&self, crtc_index: u32, fourcc: u32) -> Vec<u64> {
let mut modifiers: Vec<u64> = self
.for_crtc(crtc_index)
.filter(|plane| plane.plane_type != PlaneType::Cursor)
.flat_map(|plane| plane.format_table.modifiers_for(fourcc))
.map(|modifier| modifier.0)
.collect();
modifiers.sort_unstable();
modifiers.dedup();
modifiers
}
pub fn force_disable_candidates(
&self,
crtc_index: u32,
) -> impl Iterator<Item = &PlaneCapabilities> + '_ {
self.for_crtc(crtc_index)
.filter(|plane| plane.plane_type != PlaneType::Cursor)
}
#[must_use]
pub fn by_id(&self, plane_id: u32) -> Option<&PlaneCapabilities> {
self.planes.iter().find(|plane| plane.id == plane_id)
}
#[must_use]
pub fn len(&self) -> usize {
self.planes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.planes.is_empty()
}
}
#[must_use]
pub const fn rescale_alpha(value: u64, alpha_max: u64) -> u64 {
const FULL: u64 = 0xFFFF;
let clamped = if value < FULL { value } else { FULL };
if alpha_max >= FULL {
return clamped;
}
(clamped * alpha_max + FULL / 2) / FULL
}