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** — except with a
74 /// `count` of zero, which, as upstream, returns early and leaves an
75 /// existing claim in place. A caller responding to a mode change asks
76 /// again with a new count, and the old claim would otherwise keep planes
77 /// it no longer wants out of everyone else's reach; to give them all up,
78 /// call [`release`](Self::release).
79 ///
80 /// # Errors
81 ///
82 /// [`ReserveError::Shortfall`] when fewer than `count` planes match. This
83 /// CRTC's previous claim has already been released and nothing new is
84 /// claimed — no partial fill: a caller that asked for three and got two
85 /// would have to work out which of its decorations to drop.
86 pub fn reserve(
87 &mut self,
88 registry: &PlaneRegistry,
89 crtc_index: u32,
90 fourcc: u32,
91 count: usize,
92 min_zpos: u64,
93 ) -> Result<Vec<u32>, ReserveError> {
94 if count == 0 {
95 // Records the CRTC if it held nothing, and otherwise leaves its
96 // existing claim alone, as upstream does: nothing is released on
97 // this path. `reserved_for` answers with a slice either way.
98 self.by_crtc.entry(crtc_index).or_default();
99 return Ok(Vec::new());
100 }
101
102 self.release(crtc_index);
103
104 let mut candidates: Vec<(u64, u32)> = registry
105 .for_crtc(crtc_index)
106 .filter(|plane| plane.plane_type == PlaneType::Overlay)
107 // Decorations are dumb buffers, so linear.
108 .filter(|plane| plane.scans_out_linear(fourcc))
109 .filter(|plane| !self.all.contains(&plane.id))
110 .filter_map(|plane| match plane.zpos_min {
111 // A plane that advertises no zpos cannot be shown to sit above
112 // anything, so it qualifies only where nothing was required.
113 None => (min_zpos == 0).then_some((0, plane.id)),
114 Some(zpos) => (zpos >= min_zpos).then_some((zpos, plane.id)),
115 })
116 .collect();
117
118 if candidates.len() < count {
119 return Err(ReserveError::Shortfall {
120 crtc_index,
121 wanted: count,
122 found: candidates.len(),
123 });
124 }
125
126 // By zpos, then by id: two planes at the same zpos would otherwise
127 // come back in whatever order the registry happened to hold them, and
128 // a caller stacking decorations by index would get a different result
129 // run to run.
130 candidates.sort_unstable();
131
132 let claimed: Vec<u32> = candidates
133 .into_iter()
134 .take(count)
135 .map(|(_, id)| id)
136 .collect();
137 self.all.extend(claimed.iter().copied());
138 self.by_crtc.insert(crtc_index, claimed.clone());
139 Ok(claimed)
140 }
141
142 /// Give back everything this CRTC holds.
143 ///
144 /// A CRTC that holds nothing, or was never reserved for, is a no-op —
145 /// both are what a caller tearing down an output does, sometimes twice.
146 pub fn release(&mut self, crtc_index: u32) {
147 if let Some(planes) = self.by_crtc.remove(&crtc_index) {
148 for plane in planes {
149 self.all.remove(&plane);
150 }
151 }
152 }
153
154 /// What this CRTC holds, in zpos order.
155 #[must_use]
156 pub fn reserved_for(&self, crtc_index: u32) -> &[u32] {
157 self.by_crtc.get(&crtc_index).map_or(&[], Vec::as_slice)
158 }
159
160 /// Every claimed plane, across every CRTC.
161 ///
162 /// What goes to `Allocator::set_reserved_planes`, which is why it is one
163 /// list rather than a map: the allocator is told which planes it may not
164 /// have, and it does not care who has them.
165 #[must_use]
166 pub fn all_reserved(&self) -> Vec<u32> {
167 let mut all: Vec<u32> = self.all.iter().copied().collect();
168 // Sorted so the value is stable: it reaches the allocator, and a set
169 // whose order changed run to run would make a plane assignment look
170 // unstable for no reason.
171 all.sort_unstable();
172 all
173 }
174}