Skip to main content

drmkit_csd/
presenter.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Getting a drawn decoration onto the screen.
5//!
6//! Three ways, in descending order of what the hardware will do for you:
7//!
8//! - **Plane** — each decoration on its own overlay. A hover repaints only
9//!   that decoration's small buffer; nothing is recomposited.
10//! - **Composite** — blend them onto one canvas plane. Costs a canvas-sized
11//!   blend per damaged region, and needs only one plane.
12//! - **Framebuffer** — no KMS plane at all. For an fbdev fallback, where the
13//!   decoration is written into the same buffer as everything else.
14
15/// Which way a decoration reaches the screen.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum Tier {
19    /// One overlay plane per decoration.
20    Plane,
21    /// All of them blended onto one canvas plane.
22    Composite,
23    /// Into a framebuffer, with no plane involved.
24    Fb,
25}
26
27/// Pick a tier from what the hardware can offer.
28///
29/// `None` means neither a plane nor a canvas is available, and the caller
30/// should drop to its own framebuffer path — this function does not choose
31/// [`Tier::Fb`], because that tier is what a caller falls back *to* when KMS
32/// has nothing, not something to be selected among KMS options.
33///
34/// Zero desired decorations is never [`Tier::Plane`]: reserving no planes
35/// trivially "succeeds", and returning Plane on that basis would have a caller
36/// build a plane presenter with no slots.
37#[must_use]
38pub const fn choose_presenter_tier(
39    reservable: usize,
40    desired: usize,
41    has_canvas_plane: bool,
42) -> Option<Tier> {
43    if desired > 0 && reservable >= desired {
44        // Every window gets its own overlay, which is the arrangement worth
45        // having: a hover repaints one small buffer instead of a canvas.
46        return Some(Tier::Plane);
47    }
48    if has_canvas_plane {
49        // Plane-starved but not planeless: composite onto the primary.
50        return Some(Tier::Composite);
51    }
52    None
53}
54
55/// One property write, as [`compute_writes`](crate::compute_writes) and
56/// [`compute_canvas_writes`](crate::compute_canvas_writes) produce them.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[allow(
59    clippy::exhaustive_structs,
60    reason = "a value defined by its fields; there is nothing to add to it"
61)]
62pub struct PropertyWrite {
63    /// The object to write on.
64    pub object_id: u32,
65    /// Which property.
66    pub property_id: u32,
67    /// The value.
68    pub value: u64,
69}
70
71/// A decoration surface and where it goes.
72#[derive(Debug, Clone, Copy, Default)]
73#[allow(
74    clippy::exhaustive_structs,
75    reason = "built by callers with `..Default::default()`, which a new field does not break"
76)]
77pub struct SurfaceRef {
78    /// The framebuffer to scan out, or zero for nothing.
79    pub fb_id: u32,
80    /// Width in pixels.
81    pub width: u32,
82    /// Height in pixels.
83    pub height: u32,
84    /// Destination x.
85    pub x: i32,
86    /// Destination y.
87    pub y: i32,
88    /// The surface's content generation, for damage tracking.
89    pub generation: u64,
90}
91
92impl SurfaceRef {
93    /// Whether this reference names anything to draw.
94    ///
95    /// A zero framebuffer or a zero dimension is *nothing*, not an error: a
96    /// caller with fewer windows than slots passes these, and a caller mid-
97    /// resize passes them briefly.
98    #[must_use]
99    pub const fn is_armed(self) -> bool {
100        self.fb_id != 0 && self.width != 0 && self.height != 0
101    }
102}