drmkit_csd/reservation.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Claiming overlay planes for decorations, before the scene wants them.
5//!
6//! Decorations want their own planes. Compositing them into the window's
7//! buffer means the client redraws its whole surface every time the pointer
8//! crosses a button; a plane means the decoration is its own scanout layer and
9//! a hover costs a small blit.
10//!
11//! But the scene's allocator will take every free plane it can place a layer
12//! on, so a decoration that waits until commit time gets nothing. Reserving
13//! first is what makes it work — the reservation is then handed to
14//! `Allocator::set_reserved_planes`, which is the other half of the same
15//! arrangement.
16
17use std::collections::{HashMap, HashSet};
18
19use drmkit_planes::{PlaneRegistry, PlaneType};
20
21/// Why a reservation could not be made.
22#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
23#[non_exhaustive]
24pub enum ReserveError {
25 /// Not enough planes on this CRTC match what was asked for.
26 ///
27 /// Distinct from a hard failure: a caller can ask again for fewer, or
28 /// draw its decorations into the window instead. It is a shortfall, not a
29 /// broken device.
30 #[error("wanted {wanted} overlay planes on CRTC {crtc_index}, {found} are available")]
31 Shortfall {
32 /// Which CRTC.
33 crtc_index: u32,
34 /// How many were asked for.
35 wanted: usize,
36 /// How many matched.
37 found: usize,
38 },
39}
40
41/// Overlay planes claimed for decorations, per CRTC.
42#[derive(Debug, Default)]
43pub struct OverlayReservation {
44 /// What each CRTC holds, in zpos order.
45 by_crtc: HashMap<u32, Vec<u32>>,
46 /// Every claimed plane, across CRTCs.
47 ///
48 /// A separate set because a plane can be reachable from more than one
49 /// CRTC: on such hardware the first CRTC to claim it takes it, and the
50 /// second must not be offered it again. The per-CRTC map alone cannot
51 /// answer that without scanning every entry.
52 all: HashSet<u32>,
53}
54
55impl OverlayReservation {
56 /// An empty reservation.
57 #[must_use]
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 /// Claim `count` overlay planes on `crtc_index` that can scan out
63 /// `fourcc`.
64 ///
65 /// Returns them in ascending zpos order, which is the order a caller
66 /// stacks decorations in: the lowest is the one behind.
67 ///
68 /// `min_zpos` excludes planes that cannot sit above the window. A
69 /// decoration below the content it decorates is invisible, so a caller
70 /// that knows the window's stacking position passes it here rather than
71 /// discovering the problem on screen.
72 ///
73 /// **Re-reserving a CRTC releases what it held first.** A caller
74 /// responding to a mode change asks again with a new count, and the old
75 /// claim would otherwise keep planes it no longer wants out of everyone
76 /// else's reach.
77 ///
78 /// # Errors
79 ///
80 /// [`ReserveError::Shortfall`] when fewer than `count` planes match. The
81 /// reservation is left as it was — released, since the release happens
82 /// first — rather than partially filled: a caller that asked for three and
83 /// got two would have to work out which of its decorations to drop.
84 pub fn reserve(
85 &mut self,
86 registry: &PlaneRegistry,
87 crtc_index: u32,
88 fourcc: u32,
89 count: usize,
90 min_zpos: u64,
91 ) -> Result<Vec<u32>, ReserveError> {
92 if count == 0 {
93 // Still records the CRTC, so `reserved_for` answers with an empty
94 // slice rather than nothing -- a caller that asked for none has a
95 // reservation, it is just empty.
96 self.by_crtc.entry(crtc_index).or_default();
97 return Ok(Vec::new());
98 }
99
100 self.release(crtc_index);
101
102 let mut candidates: Vec<(u64, u32)> = registry
103 .for_crtc(crtc_index)
104 .filter(|plane| plane.plane_type == PlaneType::Overlay)
105 // Decorations are dumb buffers, so linear.
106 .filter(|plane| plane.scans_out_linear(fourcc))
107 .filter(|plane| !self.all.contains(&plane.id))
108 .filter_map(|plane| match plane.zpos_min {
109 // A plane that advertises no zpos cannot be shown to sit above
110 // anything, so it qualifies only where nothing was required.
111 None => (min_zpos == 0).then_some((0, plane.id)),
112 Some(zpos) => (zpos >= min_zpos).then_some((zpos, plane.id)),
113 })
114 .collect();
115
116 if candidates.len() < count {
117 return Err(ReserveError::Shortfall {
118 crtc_index,
119 wanted: count,
120 found: candidates.len(),
121 });
122 }
123
124 // By zpos, then by id: two planes at the same zpos would otherwise
125 // come back in whatever order the registry happened to hold them, and
126 // a caller stacking decorations by index would get a different result
127 // run to run.
128 candidates.sort_unstable();
129
130 let claimed: Vec<u32> = candidates
131 .into_iter()
132 .take(count)
133 .map(|(_, id)| id)
134 .collect();
135 self.all.extend(claimed.iter().copied());
136 self.by_crtc.insert(crtc_index, claimed.clone());
137 Ok(claimed)
138 }
139
140 /// Give back everything this CRTC holds.
141 ///
142 /// A CRTC that holds nothing, or was never reserved for, is a no-op —
143 /// both are what a caller tearing down an output does, sometimes twice.
144 pub fn release(&mut self, crtc_index: u32) {
145 if let Some(planes) = self.by_crtc.remove(&crtc_index) {
146 for plane in planes {
147 self.all.remove(&plane);
148 }
149 }
150 }
151
152 /// What this CRTC holds, in zpos order.
153 #[must_use]
154 pub fn reserved_for(&self, crtc_index: u32) -> &[u32] {
155 self.by_crtc.get(&crtc_index).map_or(&[], Vec::as_slice)
156 }
157
158 /// Every claimed plane, across every CRTC.
159 ///
160 /// What goes to `Allocator::set_reserved_planes`, which is why it is one
161 /// list rather than a map: the allocator is told which planes it may not
162 /// have, and it does not care who has them.
163 #[must_use]
164 pub fn all_reserved(&self) -> Vec<u32> {
165 let mut all: Vec<u32> = self.all.iter().copied().collect();
166 // Sorted so the value is stable: it reaches the allocator, and a set
167 // whose order changed run to run would make a plane assignment look
168 // unstable for no reason.
169 all.sort_unstable();
170 all
171 }
172}