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** — 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}