use drmkit_fmt::{BandwidthClass, Modifier, layer_fits_plane, scanout_cost_bytes};
use crate::layer::{ContentType, Layer, Rect};
use crate::prop::PropTag;
use crate::registry::{PlaneCapabilities, PlaneType};
const COMPRESSED_RATIO: f32 = drmkit_fmt::DEFAULT_COMPRESSED_RATIO;
#[must_use]
pub const fn bandwidth_class_bonus(class: BandwidthClass) -> i32 {
match class {
BandwidthClass::Compression => 2,
BandwidthClass::Tiling => 1,
BandwidthClass::Linear => 0,
}
}
#[must_use]
pub const fn cost_bias(scanout_bytes: u64) -> i32 {
const MB: u64 = 1024 * 1024;
if scanout_bytes >= 16 * MB {
3 } else if scanout_bytes >= 4 * MB {
2 } else if scanout_bytes >= MB {
1 } else {
0
}
}
#[must_use]
pub fn layer_priority(layer: &Layer) -> i32 {
if layer.content_type() == ContentType::Video {
return 100;
}
if layer.update_hz() > 30 {
return 80;
}
if layer.update_hz() > 0 {
return 50;
}
10
}
#[must_use]
pub fn keep_priority(layer: &Layer) -> i32 {
layer_priority(layer) * 256 + i32::from(layer.app_priority())
}
pub const WARM_STABILITY_BONUS: i32 = 100;
#[derive(Debug, Clone, Copy, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct ScoreContext {
pub held_last_frame: bool,
pub failure_hits: u32,
}
#[must_use]
pub fn score_pair(plane: &PlaneCapabilities, layer: &Layer, context: ScoreContext) -> i32 {
let mut score = 0;
if let Some(fourcc) = layer.format()
&& layer_fits_plane(&plane.format_table, fourcc, Modifier(layer.modifier()))
{
score += 4;
}
let is_composition = layer.is_composition_layer();
if is_composition && plane.plane_type == PlaneType::Primary {
score += 8;
}
let zpos = layer.property(PropTag::Zpos);
if !is_composition
&& plane.plane_type == PlaneType::Primary
&& let Some(z) = zpos
{
match plane.zpos_min {
Some(min) if z == min => score += 10,
None if z == 0 => score += 10,
_ => {}
}
}
if !is_composition && plane.plane_type == PlaneType::Overlay {
score += 2;
if let Some(z) = zpos
&& plane.zpos_min.is_none()
&& z > 0
{
score += 8;
}
}
if let Some(fourcc) = layer.format() {
let class = plane.bandwidth_class(layer.modifier());
score += bandwidth_class_bonus(class);
score += cost_bias(scanout_cost_bytes(
layer.width(),
layer.height(),
fourcc,
class,
COMPRESSED_RATIO,
));
}
score += layer_priority(layer) / 10;
if context.held_last_frame {
score += WARM_STABILITY_BONUS;
}
score -= i32::try_from(context.failure_hits).unwrap_or(i32::MAX);
score
}
#[must_use]
pub fn plane_statically_compatible(
plane: &PlaneCapabilities,
layer: &Layer,
crtc_index: u32,
) -> bool {
if layer.is_force_composited() || layer.is_transient_composited() {
return false;
}
if !plane.compatible_with_crtc(crtc_index) {
return false;
}
let Some(fourcc) = layer.format() else {
return false;
};
if !layer_fits_plane(&plane.format_table, fourcc, Modifier(layer.modifier())) {
return false;
}
let rotation = layer.rotation();
if rotation != 0 && plane.rotation_bits & rotation != rotation {
return false;
}
if layer.requires_scaling() && !plane.supports_scaling {
return false;
}
if plane.plane_type == PlaneType::Cursor {
if plane.cursor_max_w > 0 && layer.width() > plane.cursor_max_w {
return false;
}
if plane.cursor_max_h > 0 && layer.height() > plane.cursor_max_h {
return false;
}
}
true
}
#[must_use]
pub fn rects_intersect(a: Rect, b: Rect) -> bool {
let (ax, ay) = (i64::from(a.x), i64::from(a.y));
let (bx, by) = (i64::from(b.x), i64::from(b.y));
ax + i64::from(a.w) > bx
&& bx + i64::from(b.w) > ax
&& ay + i64::from(a.h) > by
&& by + i64::from(b.h) > ay
}
#[must_use]
pub fn layers_intersect(a: &Layer, b: &Layer) -> bool {
rects_intersect(a.crtc_rect(), b.crtc_rect())
}
fn union_find_root(parent: &mut [usize], mut x: usize) -> usize {
while parent[x] != x {
parent[x] = parent[parent[x]]; x = parent[x];
}
x
}
#[must_use]
pub fn split_independent_groups(layers: &[&Layer]) -> Vec<Vec<usize>> {
independent_groups(
layers,
|a, b| layers_intersect(a, b),
|layer| keep_priority(layer),
)
}
pub(crate) fn independent_groups<T, K: Ord>(
items: &[T],
intersect: impl Fn(&T, &T) -> bool,
priority: impl Fn(&T) -> K,
) -> Vec<Vec<usize>> {
let mut parent: Vec<usize> = (0..items.len()).collect();
for i in 0..items.len() {
for j in (i + 1)..items.len() {
if intersect(&items[i], &items[j]) {
let (a, b) = (
union_find_root(&mut parent, i),
union_find_root(&mut parent, j),
);
if a != b {
parent[a.max(b)] = a.min(b);
}
}
}
}
let mut groups: Vec<Vec<usize>> = Vec::new();
let mut slot = vec![usize::MAX; items.len()];
for i in 0..items.len() {
let root = union_find_root(&mut parent, i);
if slot[root] == usize::MAX {
slot[root] = groups.len();
groups.push(Vec::new());
}
groups[slot[root]].push(i);
}
groups.sort_by_cached_key(|group| {
std::cmp::Reverse(group.iter().map(|&i| priority(&items[i])).max())
});
groups
}