Skip to main content

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}