use std::cell::Cell;
use std::hash::{Hash as _, Hasher as _};
use crate::prop::{NUM_PROPS, PropClass, PropTag};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[non_exhaustive]
pub enum ContentType {
#[default]
Generic,
Video,
Ui,
Cursor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "a value defined by its fields; there is nothing to add to it"
)]
pub struct Rect {
pub x: i32,
pub y: i32,
pub w: u32,
pub h: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PropertySnapshot {
values: [u64; NUM_PROPS],
set_mask: u16,
}
impl PropertySnapshot {
#[must_use]
pub const fn get(&self, tag: PropTag) -> Option<u64> {
if self.set_mask & (1 << tag.index()) == 0 {
None
} else {
Some(self.values[tag.index()])
}
}
#[must_use]
pub const fn with(mut self, tag: PropTag, value: u64) -> Self {
self.values[tag.index()] = value;
self.set_mask |= 1 << tag.index();
self
}
}
#[expect(
clippy::struct_excessive_bools,
reason = "independently-owned placement flags; see the type documentation"
)]
#[derive(Debug, Clone)]
pub struct Layer {
values: [u64; NUM_PROPS],
set_mask: u16,
cached_hash: Cell<Option<u64>>,
force_composited: bool,
transient_composited: bool,
dirty: bool,
composition_output: bool,
externally_bound: bool,
pinned: bool,
assigned_plane: Option<u32>,
content_type: ContentType,
update_hz: u32,
app_priority: u8,
}
impl Default for Layer {
fn default() -> Self {
Self::new()
}
}
impl Layer {
#[must_use]
pub const fn new() -> Self {
Self {
values: [0; NUM_PROPS],
set_mask: 0,
cached_hash: Cell::new(None),
force_composited: false,
transient_composited: false,
dirty: true,
composition_output: false,
externally_bound: false,
pinned: false,
assigned_plane: None,
content_type: ContentType::Generic,
update_hz: 0,
app_priority: 0,
}
}
pub fn set_property(&mut self, tag: PropTag, value: u64) -> &mut Self {
let index = tag.index();
let was_set = self.set_mask & (1 << index) != 0;
let changed = !was_set || self.values[index] != value;
self.values[index] = value;
self.set_mask |= 1 << index;
if changed {
self.cached_hash.set(None);
self.dirty = true;
}
self
}
pub fn set_property_by_name(&mut self, name: &str, value: u64) -> bool {
match PropTag::from_name(name) {
Some(tag) => {
self.set_property(tag, value);
true
}
None => false,
}
}
#[must_use]
pub const fn property(&self, tag: PropTag) -> Option<u64> {
if self.set_mask & (1 << tag.index()) == 0 {
None
} else {
Some(self.values[tag.index()])
}
}
#[must_use]
pub fn property_by_name(&self, name: &str) -> Option<u64> {
PropTag::from_name(name).and_then(|tag| self.property(tag))
}
pub fn properties(&self) -> impl Iterator<Item = (PropTag, u64)> + '_ {
PropTag::ALL
.into_iter()
.filter_map(|tag| self.property(tag).map(|value| (tag, value)))
}
#[must_use]
pub const fn property_count(&self) -> u32 {
self.set_mask.count_ones()
}
#[must_use]
pub const fn snapshot(&self) -> PropertySnapshot {
PropertySnapshot {
values: self.values,
set_mask: self.set_mask,
}
}
pub fn disable(&mut self) -> &mut Self {
self.set_property(PropTag::FbId, 0)
}
#[must_use]
pub fn property_hash(&self) -> u64 {
if let Some(cached) = self.cached_hash.get() {
return cached;
}
let mut hash = GOLDEN_RATIO;
for tag in PropTag::ALL {
if tag.class() == PropClass::Content {
continue;
}
let Some(value) = self.property(tag) else {
continue;
};
hash = combine(hash, tag.index() as u64);
hash = combine(hash, value);
}
self.cached_hash.set(Some(hash));
hash
}
pub fn set_force_composited(&mut self, composited: bool) -> &mut Self {
self.force_composited = composited;
self
}
#[must_use]
pub const fn is_force_composited(&self) -> bool {
self.force_composited
}
pub fn set_transient_composited(&mut self, composited: bool) -> &mut Self {
self.transient_composited = composited;
self
}
#[must_use]
pub const fn is_transient_composited(&self) -> bool {
self.transient_composited
}
#[must_use]
pub const fn needs_composition(&self) -> bool {
self.force_composited || self.transient_composited
}
pub fn set_composition_layer(&mut self, is_composition: bool) -> &mut Self {
self.composition_output = is_composition;
self
}
#[must_use]
pub const fn is_composition_layer(&self) -> bool {
self.composition_output
}
#[must_use]
pub const fn is_externally_bound(&self) -> bool {
self.externally_bound
}
pub fn set_externally_bound(&mut self, externally_bound: bool) -> &mut Self {
self.externally_bound = externally_bound;
self
}
#[must_use]
pub const fn is_pinned(&self) -> bool {
self.pinned
}
pub fn set_pinned(&mut self, pinned: bool) -> &mut Self {
self.pinned = pinned;
self
}
#[must_use]
pub const fn assigned_plane(&self) -> Option<u32> {
self.assigned_plane
}
pub fn set_assigned_plane(&mut self, plane_id: Option<u32>) -> &mut Self {
self.assigned_plane = plane_id;
self
}
#[must_use]
pub const fn content_type(&self) -> ContentType {
self.content_type
}
pub fn set_content_type(&mut self, content_type: ContentType) -> &mut Self {
self.content_type = content_type;
self
}
#[must_use]
pub const fn update_hz(&self) -> u32 {
self.update_hz
}
pub fn set_update_hint(&mut self, hz: u32) -> &mut Self {
self.update_hz = hz;
self
}
#[must_use]
pub const fn app_priority(&self) -> u8 {
self.app_priority
}
pub fn set_app_priority(&mut self, priority: u8) -> &mut Self {
self.app_priority = priority;
self
}
#[must_use]
pub const fn is_dirty(&self) -> bool {
self.dirty
}
pub const fn mark_clean(&mut self) {
self.dirty = false;
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
reason = "KMS carries these as 32-bit fields in u64 property slots"
)]
pub const fn format(&self) -> Option<u32> {
match self.property(PropTag::PixelFormat) {
Some(value) => Some(value as u32),
None => None,
}
}
#[must_use]
pub const fn modifier(&self) -> u64 {
match self.property(PropTag::FbModifier) {
Some(value) => value,
None => 0,
}
}
#[must_use]
pub const fn rotation(&self) -> u64 {
match self.property(PropTag::Rotation) {
Some(value) => value,
None => 0,
}
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
reason = "KMS carries these as 32-bit fields in u64 property slots"
)]
pub const fn width(&self) -> u32 {
match self.property(PropTag::CrtcW) {
Some(value) => value as u32,
None => 0,
}
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
reason = "KMS carries these as 32-bit fields in u64 property slots"
)]
pub const fn height(&self) -> u32 {
match self.property(PropTag::CrtcH) {
Some(value) => value as u32,
None => 0,
}
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
reason = "KMS carries CRTC_X/Y as signed 32-bit fields in u64 property slots"
)]
pub const fn crtc_rect(&self) -> Rect {
let x = match self.property(PropTag::CrtcX) {
Some(value) => value as i32,
None => 0,
};
let y = match self.property(PropTag::CrtcY) {
Some(value) => value as i32,
None => 0,
};
Rect {
x,
y,
w: self.width(),
h: self.height(),
}
}
#[must_use]
pub const fn requires_scaling(&self) -> bool {
let (Some(src_w), Some(crtc_w), Some(src_h), Some(crtc_h)) = (
self.property(PropTag::SrcW),
self.property(PropTag::CrtcW),
self.property(PropTag::SrcH),
self.property(PropTag::CrtcH),
) else {
return false;
};
src_w != (crtc_w << 16) || src_h != (crtc_h << 16)
}
}
const GOLDEN_RATIO: u64 = 0x9e37_79b9_7f4a_7c15;
fn combine(hash: u64, value: u64) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
value.hash(&mut hasher);
hash ^ (hasher
.finish()
.wrapping_add(GOLDEN_RATIO)
.wrapping_add(hash << 6)
.wrapping_add(hash >> 2))
}